|
85367
|
2922
|
13
|
2026-05-28T12:21:25.729483+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970885729_m1.jpg...
|
Slack
|
Activity - Jiminny Inc - 2 new items - Slack
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
All, 2 unread
DMs
Mentions
Threads
Add new tab
Vasil Vasilev
DM
Mark as unread
Clear
More actions
ще си го оставя както е
Stefka Stoyanova
DM
13 mins
извинявай, че така стана
Iliyana Netseva
DM
17 mins
ima li prichina tova da e napraveno po tozi nachin ili prosto taka e napraveno nqkoga
Stoyan Tomov
DM
1:17 PM
и имам и да добавя още нещо към него
Approved Toast
App
12:00 PM
#12136 JY-20915 fix missing header
#12136 JY-20915 fix missing header
by you
Petko Kashinski
DM
10:24 AM
Тенкс !
Galya Dimitrova
DM
10:14 AM
по скоро да ми пише за да видим дали ще го планираме
Petko Kashinski
Reacted in
DM
9:40 AM
You:
ще проверя
1 reaction, react with star2 emoji
1
Yesterday
Todor Stamatov
DM
Yesterday
готово
Steliyan Georgiev
DM
Yesterday
дай ми 3 мин и ще ти звънна
Steliyan Georgiev
Reacted in
DM
Yesterday
You:
само да видя къде може да се покаже
1 reaction, react with +1 emoji
1
Steliyan Georgiev
Reacted in
DM
Yesterday
You:
[URL_WITH_CREDENTIALS] Dimitrova
@Galya Dimitrova
assigned a Story from Unassigned ⟶ you
…
Tuesday, May 26th
Select a notification to view the details....
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"All, 2 unread","depth":11,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXRadioButton","text":"DMs","depth":11,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Mentions","depth":11,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Threads","depth":11,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Add new tab","depth":12,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Vasil Vasilev","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Mark as unread","depth":16,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Clear","depth":16,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":16,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"ще си го оставя както е","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Stefka Stoyanova","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"13 mins","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"извинявай, че така стана","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Iliyana Netseva","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"17 mins","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ima li prichina tova da e napraveno po tozi nachin ili prosto taka e napraveno nqkoga","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tomov","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1:17 PM","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"и имам и да добавя още нещо към него","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Approved Toast","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"App","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"12:00 PM","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"#12136 JY-20915 fix missing header","depth":16,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"#12136 JY-20915 fix missing header","depth":17,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"by you","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Petko Kashinski","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"10:24 AM","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Тенкс !","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"10:14 AM","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"по скоро да ми пише за да видим дали ще го планираме","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Petko Kashinski","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Reacted in","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"9:40 AM","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"You:","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ще проверя","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"1 reaction, react with star2 emoji","depth":16,"on_screen":true,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":17,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Yesterday","depth":14,"bounds":{"left":0.6201389,"top":0.0,"width":0.039583333,"height":0.017777778},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Todor Stamatov","depth":15,"bounds":{"left":0.5798611,"top":0.0,"width":0.072222225,"height":0.02},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"bounds":{"left":0.5798611,"top":0.0,"width":0.015277778,"height":0.0033333334},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Yesterday","depth":15,"bounds":{"left":0.6701389,"top":0.0,"width":0.038194444,"height":0.017777778},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"готово","depth":16,"bounds":{"left":0.54930556,"top":0.0,"width":0.031944446,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":15,"bounds":{"left":0.5798611,"top":0.0,"width":0.07986111,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"bounds":{"left":0.5798611,"top":0.0,"width":0.015277778,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Yesterday","depth":15,"bounds":{"left":0.6701389,"top":0.0,"width":0.038194444,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"дай ми 3 мин и ще ти звънна","depth":16,"bounds":{"left":0.54930556,"top":0.0,"width":0.13958333,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":15,"bounds":{"left":0.5798611,"top":0.0,"width":0.07986111,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Reacted in","depth":15,"bounds":{"left":0.5798611,"top":0.0,"width":0.041666668,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"bounds":{"left":0.62291664,"top":0.0,"width":0.015277778,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Yesterday","depth":15,"bounds":{"left":0.6701389,"top":0.0,"width":0.038194444,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"You:","depth":16,"bounds":{"left":0.54930556,"top":0.0,"width":0.02013889,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"само да видя къде може да се покаже","depth":16,"bounds":{"left":0.54930556,"top":0.0,"width":0.17916666,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"1 reaction, react with +1 emoji","depth":16,"bounds":{"left":0.54930556,"top":0.0,"width":0.029861111,"height":0.0011111111},"on_screen":true,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":17,"bounds":{"left":0.56875,"top":0.0,"width":0.0048611113,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":15,"bounds":{"left":0.5798611,"top":0.0,"width":0.07986111,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Reacted in","depth":15,"bounds":{"left":0.5798611,"top":0.0,"width":0.041666668,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"bounds":{"left":0.62291664,"top":0.0,"width":0.015277778,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Yesterday","depth":15,"bounds":{"left":0.6701389,"top":0.0,"width":0.038194444,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"You:","depth":16,"bounds":{"left":0.54930556,"top":0.0,"width":0.02013889,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"https://jiminny.atlassian.net/browse/JY-20613","depth":16,"bounds":{"left":0.54930556,"top":0.0,"width":0.17986111,"height":0.0011111111},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"https://jiminny.atlassian.net/browse/JY-20613","depth":17,"bounds":{"left":0.54930556,"top":0.0,"width":0.175,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"1 reaction, react with +1 emoji","depth":16,"bounds":{"left":0.54930556,"top":0.0,"width":0.029861111,"height":0.0011111111},"on_screen":true,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":17,"bounds":{"left":0.56875,"top":0.0,"width":0.0048611113,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Jira Cloud","depth":15,"bounds":{"left":0.5798611,"top":0.0,"width":0.045833334,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"App","depth":15,"bounds":{"left":0.5798611,"top":0.0,"width":0.016666668,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Yesterday","depth":15,"bounds":{"left":0.6701389,"top":0.0,"width":0.038194444,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":16,"bounds":{"left":0.56458336,"top":0.0,"width":0.0027777778,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"@Galya Dimitrova","depth":16,"bounds":{"left":0.5673611,"top":0.0,"width":0.08611111,"height":0.0011111111},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"@Galya Dimitrova","depth":17,"bounds":{"left":0.56875,"top":0.0,"width":0.083333336,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"assigned a Story from Unassigned ⟶ you","depth":16,"bounds":{"left":0.54930556,"top":0.0,"width":0.15694444,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"…","depth":16,"bounds":{"left":0.6875,"top":0.0,"width":0.008333334,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Tuesday, May 26th","depth":14,"bounds":{"left":0.6020833,"top":0.0,"width":0.07569444,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Select a notification to view the details.","depth":9,"on_screen":true,"role_description":"text"}]...
|
6527906876826181421
|
6771193393998559626
|
visual_change
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
All, 2 unread
DMs
Mentions
Threads
Add new tab
Vasil Vasilev
DM
Mark as unread
Clear
More actions
ще си го оставя както е
Stefka Stoyanova
DM
13 mins
извинявай, че така стана
Iliyana Netseva
DM
17 mins
ima li prichina tova da e napraveno po tozi nachin ili prosto taka e napraveno nqkoga
Stoyan Tomov
DM
1:17 PM
и имам и да добавя още нещо към него
Approved Toast
App
12:00 PM
#12136 JY-20915 fix missing header
#12136 JY-20915 fix missing header
by you
Petko Kashinski
DM
10:24 AM
Тенкс !
Galya Dimitrova
DM
10:14 AM
по скоро да ми пише за да видим дали ще го планираме
Petko Kashinski
Reacted in
DM
9:40 AM
You:
ще проверя
1 reaction, react with star2 emoji
1
Yesterday
Todor Stamatov
DM
Yesterday
готово
Steliyan Georgiev
DM
Yesterday
дай ми 3 мин и ще ти звънна
Steliyan Georgiev
Reacted in
DM
Yesterday
You:
само да видя къде може да се покаже
1 reaction, react with +1 emoji
1
Steliyan Georgiev
Reacted in
DM
Yesterday
You:
[URL_WITH_CREDENTIALS] Dimitrova
@Galya Dimitrova
assigned a Story from Unassigned ⟶ you
…
Tuesday, May 26th
Select a notification to view the details.
PhpStormFileEditViewNavigateCodeRefactorRunToolsGitWindowHelpec2-user@ip-10-30-129-190:~screenpipe"O ₴4‹$0(ah]A100% <78• Thu 28 May 15:21:25181ec2-user@ip-10-30-140-...₴7DOCKER#_####_#####\\###||\#/V~'881DEV (-zsh)O [EMAIL] Linux 2023 (ECS Optimized)_/m/For documentation, visit [URL_WITH_CREDENTIALS] ~]$ exitlogoutConnection to jiminny-prod-ecsi closed.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ applukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ vprodWarning: Permanently added 'jiminny-prod-ecs1' (ED25519) to the list of known hosts.A newer release of "Amazon Linux" is available.Version 2023.10.20260330:Version 2023.11.20260406:Version 2023.11.20260413:Version2023.11.20260427:Version2023.11.20260505:Version 2023.11.20260509:Version 2023.11.20260511:Version 2023.11.20260514:Run "/usr/bin/dnf check-release-update" for full release and version update info#_~\ ####_\ #####\\###1\#/Amazon Linux 2023 (ECS Optimized)/m/'For documentation, visit [URL_WITH_CREDENTIALS] ~]$ client_loop: send disconnect: Broken pipeukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-relay) $I...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85366
|
2923
|
11
|
2026-05-28T12:21:24.811364+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970884811_m2.jpg...
|
Slack
|
releases (Channel) - Jiminny Inc - 2 new items - S releases (Channel) - Jiminny Inc - 2 new items - Slack...
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
Stoyan Tomov
Petko Kashinski
Galya Dimitrova
Todor Stamatov
Steliyan Georgiev
Ves
Mira
Nikolay Yankov
James Graham
Lukas Kovalik
you
Jira Cloud
Toast
Messages
Messages
Files
Files
Bookmarks
Bookmarks
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
GitHub
APP
Today at 8:58:44 AM
8:58 AM
2 new commits
2 new commits
pushed to
master
master
by
nikolaybiaivanov
nikolaybiaivanov
2fb79795
2fb79795
- JY-20771 | Add ai_call_scores column
828865ed
828865ed
- Merge pull request #12135 from jiminny/JY-20771-call-scoring-filter-for-exec-reports-migration
jiminny/app
jiminny/app
|
Added by
GitHub
GitHub
CircleCI
APP
Today at 9:27:04 AM
9:27 AM
Deployment Successful! tada emoji
Deployment Successful!
Project
: app
When
: 05/28/2026 06:27:03
Tag
:
View Job
View Job
GitHub
APP
Today at 10:50:39 AM
10:50 AM
20 new commits
20 new commits
pushed to
master
master
by
yalokin-jiminny
yalokin-jiminny
891eb72c
891eb72c
- JY-20855 | Add MCP ES - summary
1b8845ee
1b8845ee
- JY-20855 | Add MCP ES - action_items
18b9e3df
18b9e3df
- JY-20855 | Add MCP ES - key_points
1fd201fa
1fd201fa
- JY-20855 | Add MCP ES - notes
99ec572d
99ec572d
- JY-20855 | Add MCP ES - moments
Show more
jiminny/app
jiminny/app
|
Added by
GitHub
GitHub...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"bounds":{"left":0.5056516,"top":0.058260176,"width":0.011968086,"height":0.028731046},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"bounds":{"left":0.50299203,"top":0.10055866,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":8,"bounds":{"left":0.50664896,"top":0.13806863,"width":0.009973404,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":6,"bounds":{"left":0.50299203,"top":0.15482841,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":8,"bounds":{"left":0.50764626,"top":0.19233839,"width":0.007978723,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":6,"bounds":{"left":0.50299203,"top":0.20909816,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":8,"bounds":{"left":0.5053192,"top":0.24660814,"width":0.012965426,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5053192,"top":0.24660814,"width":0.0026595744,"height":0.011173184}},{"char_start":1,"char_count":7,"bounds":{"left":0.50764626,"top":0.24660814,"width":0.010638298,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":6,"bounds":{"left":0.50299203,"top":0.26336792,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":8,"bounds":{"left":0.50797874,"top":0.3008779,"width":0.0076462766,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50797874,"top":0.3008779,"width":0.0019946808,"height":0.011173184}},{"char_start":1,"char_count":4,"bounds":{"left":0.5099734,"top":0.3008779,"width":0.0056515955,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":6,"bounds":{"left":0.50299203,"top":0.31763768,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":8,"bounds":{"left":0.50731385,"top":0.35514766,"width":0.008643617,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50731385,"top":0.35514766,"width":0.0019946808,"height":0.011173184}},{"char_start":1,"char_count":4,"bounds":{"left":0.5093085,"top":0.35514766,"width":0.0066489363,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":6,"bounds":{"left":0.50299203,"top":0.3719074,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":8,"bounds":{"left":0.50731385,"top":0.4094174,"width":0.008976064,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50731385,"top":0.4094174,"width":0.0033244682,"height":0.011173184}},{"char_start":1,"char_count":3,"bounds":{"left":0.5106383,"top":0.4094174,"width":0.0056515955,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":13,"bounds":{"left":0.5369016,"top":0.09976058,"width":0.018284574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5369016,"top":0.09976058,"width":0.003656915,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.5405585,"top":0.09976058,"width":0.01462766,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":13,"bounds":{"left":0.5369016,"top":0.12210695,"width":0.01761968,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5369016,"top":0.12210695,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.5398936,"top":0.12210695,"width":0.01462766,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":13,"bounds":{"left":0.5369016,"top":0.14445332,"width":0.018284574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5369016,"top":0.14445332,"width":0.0039893617,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54089093,"top":0.14445332,"width":0.014295213,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":13,"bounds":{"left":0.5369016,"top":0.16679968,"width":0.02925532,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5369016,"top":0.16679968,"width":0.003656915,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.5405585,"top":0.16679968,"width":0.025265958,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"1","depth":13,"bounds":{"left":0.5884308,"top":0.16759777,"width":0.0026595744,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":13,"bounds":{"left":0.5369016,"top":0.18914606,"width":0.024268618,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5369016,"top":0.18914606,"width":0.0039893617,"height":0.014365523}},{"char_start":1,"char_count":10,"bounds":{"left":0.54089093,"top":0.18914606,"width":0.020279255,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"platform-backend-engineers","depth":15,"bounds":{"left":0.5422208,"top":0.28332004,"width":0.044215426,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.28332004,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":25,"bounds":{"left":0.54521275,"top":0.28332004,"width":0.058843084,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":15,"bounds":{"left":0.5422208,"top":0.3056664,"width":0.04454787,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.3056664,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":18,"bounds":{"left":0.54521275,"top":0.3056664,"width":0.04155585,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":15,"bounds":{"left":0.5422208,"top":0.35834,"width":0.022273935,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.35834,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54488033,"top":0.35834,"width":0.019614361,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":15,"bounds":{"left":0.5422208,"top":0.38068634,"width":0.012300532,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.38068634,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":5,"bounds":{"left":0.54488033,"top":0.38068634,"width":0.00930851,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":15,"bounds":{"left":0.5422208,"top":0.40303272,"width":0.018284574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.40303272,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54521275,"top":0.40303272,"width":0.015292553,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"bugs","depth":15,"bounds":{"left":0.5422208,"top":0.4253791,"width":0.010638298,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.4253791,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":3,"bounds":{"left":0.54521275,"top":0.4253791,"width":0.00731383,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":15,"bounds":{"left":0.5422208,"top":0.44772545,"width":0.034574468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.44772545,"width":0.0023271276,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54454786,"top":0.44772545,"width":0.032247342,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"donut_time","depth":15,"bounds":{"left":0.5422208,"top":0.47007182,"width":0.025265958,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.47007182,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54521275,"top":0.47007182,"width":0.022273935,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":15,"bounds":{"left":0.5422208,"top":0.4924182,"width":0.025930852,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.4924182,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":10,"bounds":{"left":0.54488033,"top":0.4924182,"width":0.022938829,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"general","depth":15,"bounds":{"left":0.5422208,"top":0.51476455,"width":0.016289894,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.51476455,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54488033,"top":0.51476455,"width":0.013297873,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"happy_birthday","depth":15,"bounds":{"left":0.5422208,"top":0.5371109,"width":0.033909574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.5371109,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":13,"bounds":{"left":0.54521275,"top":0.5371109,"width":0.030917553,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"infosec_internal_all","depth":15,"bounds":{"left":0.5422208,"top":0.5594573,"width":0.041888297,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.5594573,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":19,"bounds":{"left":0.54355055,"top":0.5594573,"width":0.040226065,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"infra-changes","depth":15,"bounds":{"left":0.5422208,"top":0.5818037,"width":0.029587766,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.5818037,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54355055,"top":0.5818037,"width":0.02825798,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"infrastructure_dev","depth":15,"bounds":{"left":0.5422208,"top":0.60415006,"width":0.040226065,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.60415006,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":17,"bounds":{"left":0.54355055,"top":0.60415006,"width":0.038896278,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"jbu-team-info","depth":15,"bounds":{"left":0.5422208,"top":0.62649643,"width":0.029920213,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.62649643,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54355055,"top":0.62649643,"width":0.028590426,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":15,"bounds":{"left":0.5422208,"top":0.64884275,"width":0.023271276,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.64884275,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54355055,"top":0.64884275,"width":0.021609042,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"platform-team","depth":15,"bounds":{"left":0.5422208,"top":0.6711891,"width":0.03158245,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.6711891,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54521275,"top":0.6711891,"width":0.028590426,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":15,"bounds":{"left":0.5422208,"top":0.6935355,"width":0.034906916,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.6935355,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54521275,"top":0.6935355,"width":0.031914894,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":15,"bounds":{"left":0.5422208,"top":0.7158819,"width":0.03856383,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.7158819,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54521275,"top":0.7158819,"width":0.03557181,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"random","depth":15,"bounds":{"left":0.5422208,"top":0.73822826,"width":0.016954787,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.73822826,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":5,"bounds":{"left":0.54421544,"top":0.73822826,"width":0.014960106,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":15,"bounds":{"left":0.5422208,"top":0.76057464,"width":0.01761968,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.76057464,"width":0.0016622341,"height":0.014365523}},{"char_start":1,"char_count":7,"bounds":{"left":0.54388297,"top":0.76057464,"width":0.015957447,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"support","depth":15,"bounds":{"left":0.5422208,"top":0.782921,"width":0.017287234,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.782921,"width":0.0023271276,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54454786,"top":0.782921,"width":0.01462766,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":15,"bounds":{"left":0.5422208,"top":0.80526733,"width":0.024601065,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.80526733,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54421544,"top":0.80526733,"width":0.022606382,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":15,"bounds":{"left":0.5422208,"top":0.8276137,"width":0.045212764,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.8276137,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":20,"bounds":{"left":0.54421544,"top":0.8276137,"width":0.04720745,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":15,"bounds":{"left":0.5422208,"top":0.8802873,"width":0.026595745,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.8802873,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54521275,"top":0.8802873,"width":0.023271276,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Stefka Stoyanova","depth":15,"bounds":{"left":0.5422208,"top":0.90263367,"width":0.037898935,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.90263367,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54488033,"top":0.90263367,"width":0.03523936,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Iliyana Netseva","depth":15,"bounds":{"left":0.5422208,"top":0.92498004,"width":0.03324468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.92498004,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":14,"bounds":{"left":0.54355055,"top":0.92498004,"width":0.031914894,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tomov","depth":15,"bounds":{"left":0.5422208,"top":0.9473264,"width":0.030917553,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.9473264,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":11,"bounds":{"left":0.54488033,"top":0.9473264,"width":0.027925532,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Petko Kashinski","depth":15,"bounds":{"left":0.5422208,"top":0.9696728,"width":0.034574468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.9696728,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":14,"bounds":{"left":0.54521275,"top":0.9696728,"width":0.03158245,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":15,"bounds":{"left":0.5422208,"top":0.9920192,"width":0.03523936,"height":0.003990423},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Todor Stamatov","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.034574468,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.038231384,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Ves","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.007978723,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Mira","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.010305851,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.032912236,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"James Graham","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.031914894,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Lukas Kovalik","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.029587766,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"you","depth":15,"bounds":{"left":0.5744681,"top":0.9952115,"width":0.0063164895,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Jira Cloud","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.021941489,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Toast","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.011635638,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Messages","depth":9,"bounds":{"left":0.60206115,"top":0.09177973,"width":0.030917553,"height":0.030327214},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Messages","depth":11,"bounds":{"left":0.61136967,"top":0.10055866,"width":0.018949468,"height":0.012769354},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.61136967,"top":0.10055866,"width":0.0039893617,"height":0.012769354}},{"char_start":1,"char_count":7,"bounds":{"left":0.61535907,"top":0.10055866,"width":0.014960106,"height":0.012769354}}],"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":9,"bounds":{"left":0.63397604,"top":0.09177973,"width":0.020944148,"height":0.030327214},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":11,"bounds":{"left":0.64328456,"top":0.10055866,"width":0.008976064,"height":0.012769354},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.64328456,"top":0.10055866,"width":0.0026595744,"height":0.012769354}},{"char_start":1,"char_count":4,"bounds":{"left":0.6459442,"top":0.10055866,"width":0.0063164895,"height":0.012769354}}],"role_description":"text"},{"role":"AXRadioButton","text":"Bookmarks","depth":9,"bounds":{"left":0.65625,"top":0.09177973,"width":0.033909574,"height":0.030327214},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Bookmarks","depth":11,"bounds":{"left":0.6655585,"top":0.10055866,"width":0.021941489,"height":0.012769354},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.6655585,"top":0.10055866,"width":0.0029920214,"height":0.012769354}},{"char_start":1,"char_count":8,"bounds":{"left":0.6682181,"top":0.10055866,"width":0.019281914,"height":0.012769354}}],"role_description":"text"},{"role":"AXPopUpButton","text":"Add and Edit Channel Tabs","depth":9,"bounds":{"left":0.6911569,"top":0.09177973,"width":0.010970744,"height":0.030327214},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Canvas","depth":9,"bounds":{"left":0.59640956,"top":0.0518755,"width":0.015957447,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"List","depth":9,"bounds":{"left":0.59640956,"top":0.0518755,"width":0.007978723,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":9,"bounds":{"left":0.59640956,"top":0.0518755,"width":0.013962766,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXPopUpButton","text":"Jump to date","depth":14,"bounds":{"left":0.7849069,"top":0.12689546,"width":0.025265958,"height":0.022346368},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"GitHub","depth":15,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"APP","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"Today at 8:58:44 AM","depth":15,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"8:58 AM","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"2 new commits","depth":15,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2 new commits","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"pushed to","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"master","depth":16,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"master","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"by","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"nikolaybiaivanov","depth":15,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"nikolaybiaivanov","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"2fb79795","depth":18,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2fb79795","depth":19,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"- JY-20771 | Add ai_call_scores column","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"828865ed","depth":18,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"828865ed","depth":19,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"- Merge pull request #12135 from jiminny/JY-20771-call-scoring-filter-for-exec-reports-migration","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"jiminny/app","depth":17,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"jiminny/app","depth":18,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"|","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Added by","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"GitHub","depth":17,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"GitHub","depth":18,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"CircleCI","depth":15,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"APP","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"Today at 9:27:04 AM","depth":15,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"9:27 AM","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"Deployment Successful! tada emoji","depth":15,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"Deployment Successful!","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Project","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":": app","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"When","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":": 05/28/2026 06:27:03","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Tag","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":":","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"View Job","depth":16,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"View Job","depth":18,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"GitHub","depth":15,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"APP","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"Today at 10:50:39 AM","depth":15,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"10:50 AM","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"20 new commits","depth":15,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"20 new commits","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"pushed to","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"master","depth":16,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"master","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"by","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"yalokin-jiminny","depth":15,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"891eb72c","depth":18,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"891eb72c","depth":19,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"- JY-20855 | Add MCP ES - summary","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"1b8845ee","depth":18,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1b8845ee","depth":19,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"- JY-20855 | Add MCP ES - action_items","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"18b9e3df","depth":18,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"18b9e3df","depth":19,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"- JY-20855 | Add MCP ES - key_points","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"1fd201fa","depth":18,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1fd201fa","depth":19,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"- JY-20855 | Add MCP ES - notes","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"99ec572d","depth":18,"bounds":{"left":0.6246675,"top":0.12051077,"width":0.019281914,"height":0.011971269},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"99ec572d","depth":19,"bounds":{"left":0.6246675,"top":0.12051077,"width":0.019281914,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.6246675,"top":0.12051077,"width":0.0023271276,"height":0.011971269}},{"char_start":1,"char_count":7,"bounds":{"left":0.62699467,"top":0.12051077,"width":0.016954787,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"- JY-20855 | Add MCP ES - moments","depth":17,"bounds":{"left":0.6452792,"top":0.11811652,"width":0.08178192,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.6452792,"top":0.11811652,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":32,"bounds":{"left":0.64660907,"top":0.11811652,"width":0.078457445,"height":0.014365523}}],"role_description":"text"},{"role":"AXButton","text":"Show more","depth":17,"bounds":{"left":0.62333775,"top":0.13407822,"width":0.024933511,"height":0.017557861},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"jiminny/app","depth":17,"bounds":{"left":0.6299867,"top":0.15562649,"width":0.020611702,"height":0.011173184},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"jiminny/app","depth":18,"bounds":{"left":0.6299867,"top":0.15562649,"width":0.020611702,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"|","depth":17,"bounds":{"left":0.6505984,"top":0.15562649,"width":0.0029920214,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Added by","depth":17,"bounds":{"left":0.65359044,"top":0.15562649,"width":0.01761968,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"GitHub","depth":17,"bounds":{"left":0.6712101,"top":0.15562649,"width":0.012965426,"height":0.011173184},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"GitHub","depth":18,"bounds":{"left":0.6712101,"top":0.15562649,"width":0.012965426,"height":0.011173184},"on_screen":true,"role_description":"text"}]...
|
6312496119937518520
|
-8184851828724043077
|
click
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
Stoyan Tomov
Petko Kashinski
Galya Dimitrova
Todor Stamatov
Steliyan Georgiev
Ves
Mira
Nikolay Yankov
James Graham
Lukas Kovalik
you
Jira Cloud
Toast
Messages
Messages
Files
Files
Bookmarks
Bookmarks
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
GitHub
APP
Today at 8:58:44 AM
8:58 AM
2 new commits
2 new commits
pushed to
master
master
by
nikolaybiaivanov
nikolaybiaivanov
2fb79795
2fb79795
- JY-20771 | Add ai_call_scores column
828865ed
828865ed
- Merge pull request #12135 from jiminny/JY-20771-call-scoring-filter-for-exec-reports-migration
jiminny/app
jiminny/app
|
Added by
GitHub
GitHub
CircleCI
APP
Today at 9:27:04 AM
9:27 AM
Deployment Successful! tada emoji
Deployment Successful!
Project
: app
When
: 05/28/2026 06:27:03
Tag
:
View Job
View Job
GitHub
APP
Today at 10:50:39 AM
10:50 AM
20 new commits
20 new commits
pushed to
master
master
by
yalokin-jiminny
yalokin-jiminny
891eb72c
891eb72c
- JY-20855 | Add MCP ES - summary
1b8845ee
1b8845ee
- JY-20855 | Add MCP ES - action_items
18b9e3df
18b9e3df
- JY-20855 | Add MCP ES - key_points
1fd201fa
1fd201fa
- JY-20855 | Add MCP ES - notes
99ec572d
99ec572d
- JY-20855 | Add MCP ES - moments
Show more
jiminny/app
jiminny/app
|
Added by
GitHub
GitHub
SIUCKFV faVsco.is ~Projectv© VoiceConsentPrefix.phpen NoticationsnObservers© AccountObserver.php© ActivityObserver.php© ContactObserver.php© [EMAIL]© ProfileObserver.phpSocialAccountObserver.phpUserObserver.php© UserRoleObserver.php> Co PoliciesC ProvidersQueueu ReoositonieCRutesvserviceActivityAiReodrEJ AvatarTeallendarVIChRotonWindowHelp#12121 on JY-20963-fox-hService.phpphe helpers.phpDeleteObjectsTrait.phpOLeAkоkyscrco.on8 UserRoleObserverTest.phpC TextRelayServiceTest.phg(©) Hubspot.JournalPollinaService.ohp x*, 88cass nuosco ourhalroto noserv* Acquire exclusive polling lock to prevent aultiple instancesS &rivate function acquirePollingLock(): boolBullhorne copperimermobiacteDecorateActivityDummy› @ Helpersv im HibsodAccountSyncStrategyMAetioneContactSyncStrategy331332› DODTO› E Fieldswn taueea© HubspotClientCredenti339@HubspotDealWebhook 340© HubspotJournalApiCie341© HubspotJourmalPotingt 342© HubspotWebhookSubs© JournalApiResult.php© JournalßatchSizeLimitE© JournalEventTransforma MetadataOpportunitySyncStrategy→lmlpae natioo* Rerease the potting lockrivate function releasePollingLock(): voidf...,* Force release the polling lock (for emergency situations)ublic function forceReleaseLock(): voidf...* Signat the polling service to stop gracefullyublic function requestStop(): void{...}©ActivityController.php© Kernel.phpE custom.logA console (STAGING)# laravel.logA12X9 A V—782RRR8733=713-234715'EX', self::LOCK,717722723BRRERE22TX: AUTONAND a.created_at › DATE_SGROUP BY u.id, v.email,ORDER BY sne count DESCselects tron rocesSELECTCoNcAl UIdo CASc whsa.*,t.ouner_id FROM socieJuew users u on U.10 = SJoik ceansWHERE v.tean_id = 1117 arSalae * rroN acuvoeSELECT * FROM activitiossaee * FroN cra contaSELECT * FROM teans WHERHselee ron usens whenselect * fron playbooksseleetron miawhonkeselect * fron playbook_caselect * fron crn_fieldsselect * fron crn_field_SELECT * FROM crn_field_oAOTM con Brelds 4 0M 4• JOIN activities a ON $6WHERE activity_id = 7993:# AND f.crn provider 1dSELECT * FROM activity_meselect * fron text_relaysselect * fron activitiesBelcex tron uer whelselect x fron activitiesselect x fron accounts wiselect & fron teans wherselect & fron actvitysHomeActivityJiminnY ….68 Unrend.@ Threads6a Huddles* Drafts & sent® DirectoriesAb External connections* Starred8 platform-backend-=oktormeinner-team(6) Channels# ai-chapter# alerts# backend# bugs# confusion-clinic# donut time# engineeringgenerall# happy_birthday8 infosec_internal_all# infra-changes# infrastructure_devwthusteameint# jiminny-bg# platform-team# platform-tickets# product_launchessrandom# releases# support# thank-yous# the_people_of_jimi.Direct messages#. Vasil VasilevF8. Stefka StoyanovaL. lliyana Netsevaa Stovan TomowPetko Kashinski01Q Describe what you are looking for# releasesBookmarks99ec572d - JY-20855 |Add MCP ES - momentsDooywnny/app Added by GitHutGitHub APP 11:09 AM1 new commit pushed to master by yalokin-liminny96465d0b - Fix stage prohle for remote commands runwiano Added ov ctHubCircleC] APP 11:16 AMO Deployment Sucesiul eProject: appWhen: 05/28/2026 08:16:27View obGitHub APP 12:27 PM3 new commits pushed to master by LakyLakdalcb1d1 - JY-20915 fix missins header(71898ade - Merge branch master into JY-20915-fix-missing-header-text-relaybc8d03d9 - Merge pull request #12136 from jiminny/JY-20915-fix-missing-header-Kxothy© fiminme/apo Added by GitHut:CircleCI APP 12-53 PMDeployment Successful!Protect aorWhen: 05/28/2026 09:53:14View JobGIHIND AXP 3H4PN20 new commits oushed to master oy Todorstamatou minm(65fe479f) - JY-208020: fix kerneloroeteoornx codt smelle95097136 - Merge branch'master' into JY-208020-salesforce-zoom-integration3741411-Y-208020: hx imolementation89h62c9 - JY-208020: add tack recolvine, fx crm orosnect resolvineShow more@ jiminny/app Added by GitHutMessage #releases8 22Inu co woy tocie20$...
|
85365
|
NULL
|
NULL
|
NULL
|
|
85365
|
2923
|
10
|
2026-05-28T12:21:21.988420+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970881988_m2.jpg...
|
Slack
|
Iliyana Netseva (DM) - Jiminny Inc - 2 new items - Iliyana Netseva (DM) - Jiminny Inc - 2 new items - Slack...
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
Stoyan Tomov
Petko Kashinski
Galya Dimitrova
Todor Stamatov
Steliyan Georgiev
Ves
Mira...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"bounds":{"left":0.5056516,"top":0.058260176,"width":0.011968086,"height":0.028731046},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"bounds":{"left":0.50299203,"top":0.10055866,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":8,"bounds":{"left":0.50664896,"top":0.13806863,"width":0.009973404,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":6,"bounds":{"left":0.50299203,"top":0.15482841,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":8,"bounds":{"left":0.50764626,"top":0.19233839,"width":0.007978723,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":6,"bounds":{"left":0.50299203,"top":0.20909816,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":8,"bounds":{"left":0.5053192,"top":0.24660814,"width":0.012965426,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5053192,"top":0.24660814,"width":0.0026595744,"height":0.011173184}},{"char_start":1,"char_count":7,"bounds":{"left":0.50764626,"top":0.24660814,"width":0.010638298,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":6,"bounds":{"left":0.50299203,"top":0.26336792,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":8,"bounds":{"left":0.50797874,"top":0.3008779,"width":0.0076462766,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50797874,"top":0.3008779,"width":0.0019946808,"height":0.011173184}},{"char_start":1,"char_count":4,"bounds":{"left":0.5099734,"top":0.3008779,"width":0.0056515955,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":6,"bounds":{"left":0.50299203,"top":0.31763768,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":8,"bounds":{"left":0.50731385,"top":0.35514766,"width":0.008643617,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50731385,"top":0.35514766,"width":0.0019946808,"height":0.011173184}},{"char_start":1,"char_count":4,"bounds":{"left":0.5093085,"top":0.35514766,"width":0.0066489363,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":6,"bounds":{"left":0.50299203,"top":0.3719074,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":8,"bounds":{"left":0.50731385,"top":0.4094174,"width":0.008976064,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50731385,"top":0.4094174,"width":0.0033244682,"height":0.011173184}},{"char_start":1,"char_count":3,"bounds":{"left":0.5106383,"top":0.4094174,"width":0.0056515955,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":13,"bounds":{"left":0.5369016,"top":0.09976058,"width":0.018949468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5369016,"top":0.09976058,"width":0.003656915,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.5405585,"top":0.09976058,"width":0.015292553,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":13,"bounds":{"left":0.5369016,"top":0.12210695,"width":0.01761968,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5369016,"top":0.12210695,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.5398936,"top":0.12210695,"width":0.01462766,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":13,"bounds":{"left":0.5369016,"top":0.14445332,"width":0.018284574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5369016,"top":0.14445332,"width":0.0039893617,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54089093,"top":0.14445332,"width":0.014295213,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":13,"bounds":{"left":0.5369016,"top":0.16679968,"width":0.02925532,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5369016,"top":0.16679968,"width":0.003656915,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.5405585,"top":0.16679968,"width":0.025265958,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"1","depth":13,"bounds":{"left":0.5884308,"top":0.16759777,"width":0.0026595744,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":13,"bounds":{"left":0.5369016,"top":0.18914606,"width":0.024268618,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5369016,"top":0.18914606,"width":0.0039893617,"height":0.014365523}},{"char_start":1,"char_count":10,"bounds":{"left":0.54089093,"top":0.18914606,"width":0.020279255,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"platform-backend-engineers","depth":15,"bounds":{"left":0.5422208,"top":0.28332004,"width":0.044215426,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.28332004,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":25,"bounds":{"left":0.54521275,"top":0.28332004,"width":0.058843084,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":15,"bounds":{"left":0.5422208,"top":0.3056664,"width":0.04454787,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.3056664,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":18,"bounds":{"left":0.54521275,"top":0.3056664,"width":0.04155585,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":15,"bounds":{"left":0.5422208,"top":0.35834,"width":0.022273935,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.35834,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54488033,"top":0.35834,"width":0.019614361,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":15,"bounds":{"left":0.5422208,"top":0.38068634,"width":0.012300532,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.38068634,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":5,"bounds":{"left":0.54488033,"top":0.38068634,"width":0.00930851,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":15,"bounds":{"left":0.5422208,"top":0.40303272,"width":0.018284574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.40303272,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54521275,"top":0.40303272,"width":0.015292553,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"bugs","depth":15,"bounds":{"left":0.5422208,"top":0.4253791,"width":0.010638298,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.4253791,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":3,"bounds":{"left":0.54521275,"top":0.4253791,"width":0.00731383,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":15,"bounds":{"left":0.5422208,"top":0.44772545,"width":0.034574468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.44772545,"width":0.0023271276,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54454786,"top":0.44772545,"width":0.032247342,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"donut_time","depth":15,"bounds":{"left":0.5422208,"top":0.47007182,"width":0.025265958,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.47007182,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54521275,"top":0.47007182,"width":0.022273935,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":15,"bounds":{"left":0.5422208,"top":0.4924182,"width":0.025930852,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.4924182,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":10,"bounds":{"left":0.54488033,"top":0.4924182,"width":0.022938829,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"general","depth":15,"bounds":{"left":0.5422208,"top":0.51476455,"width":0.016289894,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.51476455,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54488033,"top":0.51476455,"width":0.013297873,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"happy_birthday","depth":15,"bounds":{"left":0.5422208,"top":0.5371109,"width":0.033909574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.5371109,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":13,"bounds":{"left":0.54521275,"top":0.5371109,"width":0.030917553,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"infosec_internal_all","depth":15,"bounds":{"left":0.5422208,"top":0.5594573,"width":0.041888297,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.5594573,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":19,"bounds":{"left":0.54355055,"top":0.5594573,"width":0.040226065,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"infra-changes","depth":15,"bounds":{"left":0.5422208,"top":0.5818037,"width":0.029587766,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.5818037,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54355055,"top":0.5818037,"width":0.02825798,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"infrastructure_dev","depth":15,"bounds":{"left":0.5422208,"top":0.60415006,"width":0.040226065,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.60415006,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":17,"bounds":{"left":0.54355055,"top":0.60415006,"width":0.038896278,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"jbu-team-info","depth":15,"bounds":{"left":0.5422208,"top":0.62649643,"width":0.029920213,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.62649643,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54355055,"top":0.62649643,"width":0.028590426,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":15,"bounds":{"left":0.5422208,"top":0.64884275,"width":0.023271276,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.64884275,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54355055,"top":0.64884275,"width":0.021609042,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"platform-team","depth":15,"bounds":{"left":0.5422208,"top":0.6711891,"width":0.03158245,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.6711891,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54521275,"top":0.6711891,"width":0.028590426,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":15,"bounds":{"left":0.5422208,"top":0.6935355,"width":0.034906916,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.6935355,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54521275,"top":0.6935355,"width":0.031914894,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":15,"bounds":{"left":0.5422208,"top":0.7158819,"width":0.03856383,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.7158819,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54521275,"top":0.7158819,"width":0.03557181,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"random","depth":15,"bounds":{"left":0.5422208,"top":0.73822826,"width":0.016954787,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.73822826,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":5,"bounds":{"left":0.54421544,"top":0.73822826,"width":0.014960106,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":15,"bounds":{"left":0.5422208,"top":0.76057464,"width":0.018284574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.76057464,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":7,"bounds":{"left":0.54421544,"top":0.76057464,"width":0.016289894,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"support","depth":15,"bounds":{"left":0.5422208,"top":0.782921,"width":0.017287234,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.782921,"width":0.0023271276,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54454786,"top":0.782921,"width":0.01462766,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":15,"bounds":{"left":0.5422208,"top":0.80526733,"width":0.024601065,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.80526733,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54421544,"top":0.80526733,"width":0.022606382,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":15,"bounds":{"left":0.5422208,"top":0.8276137,"width":0.045212764,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.8276137,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":20,"bounds":{"left":0.54421544,"top":0.8276137,"width":0.04720745,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":15,"bounds":{"left":0.5422208,"top":0.8802873,"width":0.026595745,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.8802873,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54521275,"top":0.8802873,"width":0.023271276,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Stefka Stoyanova","depth":15,"bounds":{"left":0.5422208,"top":0.90263367,"width":0.037898935,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.90263367,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54488033,"top":0.90263367,"width":0.03523936,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Iliyana Netseva","depth":15,"bounds":{"left":0.5422208,"top":0.92498004,"width":0.03324468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.92498004,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":14,"bounds":{"left":0.54355055,"top":0.92498004,"width":0.031914894,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tomov","depth":15,"bounds":{"left":0.5422208,"top":0.9473264,"width":0.030917553,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.9473264,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":11,"bounds":{"left":0.54488033,"top":0.9473264,"width":0.027925532,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Petko Kashinski","depth":15,"bounds":{"left":0.5422208,"top":0.9696728,"width":0.034574468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.9696728,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":14,"bounds":{"left":0.54521275,"top":0.9696728,"width":0.03158245,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":15,"bounds":{"left":0.5422208,"top":0.9920192,"width":0.03523936,"height":0.003990423},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Todor Stamatov","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.034574468,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.038231384,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Ves","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.007978723,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Mira","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.010305851,"height":0.0007980846},"on_screen":true,"role_description":"text"}]...
|
4089159406747339628
|
-5801243608421254418
|
visual_change
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
Stoyan Tomov
Petko Kashinski
Galya Dimitrova
Todor Stamatov
Steliyan Georgiev
Ves
Mira
SIUCKFV faVsco.is ~Projectv© VoiceConsentPrefix.phpen NoticationsnObservers© AccountObserver.php© ActivityObserver.php© ContactObserver.php© [EMAIL]© ProfileObserver.phpSocialAccountObserver.phpUserObserver.php© UserRoleObserver.php> Co PoliciesC ProvidersQueuea ReoositonieCRutesvserviceActivityAiReodrEJ AvatarlealendarVIChRotonWindowHelp#12121 on JY-20963-fox-hService.phpphe helpers.phpDeleteObjectsTrait.phpOLeAkоkyscrco.on8 UserRoleObserverTest.phpC TextRelayServiceTest.phg(©) Hubspot.JournalPollinaService.ohp xcass nuosco ourhalroto noserv* Acquire exclusive polling lock to prevent aultiple instancesrivate function acquirePollingLock(): boolBullhorne copperimermobiacteDecorateActivityDummy› @ Helpersv im HibsodAccountSyncStrategyMAetioneContactSyncStrategy331332› DODTO› E Fieldswn taueea© HubspotClientCredenti339@HubspotDealWebhook 340© HubspotJournalApiCie341© HubspotJournalPolling: 342© HubspotWebhookSubs© JournalApiResult.php© JournalßatchSizeLimitE© JournalEventTransforma MetadataOpportunitySyncStrategy→lmlpae natioo* Rerease the potting lockrivate function releasePollingLock(): voidl...* Force release the polling lock (for emergency situations)ublic function forceReleaseLock(): voidf...* Signat the polling service to stop gracefullyublic function requestStop(): void{...}©ActivityController.php© Kernel.phpE customlogA console (STAGING)E Iaravel.logA12X9 A V—782783704733=713-234715options: "EX', self::LOCK,717-218720722722723ABREED736737738739740742742TX: AUTONAND a.created_at > DATE_SGROUP BY u.id, v.email,ORDER BY sne count DESCSELECTCONCAl U.do CASE whsa.*,t.ouner_id FROM socieJOIN users u on u.id = stJoik ceansWHERE v.tean_id = 1117 arSalae * rroN acuvoeSELECT * FROM activitiossaee * FroN cra contaSELECT * FROM teans WHERHselleetron usens whenselect * fron playbooksselect * fron playbook_caselect * fron playbook_ctselect * fron crn_fieldsselect * fron crn_field.vSELECT * FROM crn_field_oAOTM con Brelds 4 0M 4• JOIN activities a ON $6WHERE activity_id = 7993:# AND f.crn provider 1dSELECT * FROM activity_meselect * fron text_relaysselect * fron activitiesselect * fron users wherdselect x fron activitiesselect x fron accounts wiselect & fron teans wherselect & fron actvitysHomeActivityJiminny….~18 Unrends@ Threads6a Huddles* Drafts & sent® DirectoriesAb External connections* Starred8 platform-backend-=oktormeinner-team(6) Channels# ai-chapter# alerts# backend# bugs# confusion-clinic# donut time# engineering# general# happy_birthday8 infosec_internal_all# infra-changes# infrastructure_dev# jbu-team-info# jiminny-bg# platform-team# platform-tickets# product_launchessrandom# releases# support# thank-yous# the_people_of_jimi.a Direct messages#. Vasil VasilevF8. Stefka Stoyanova(T. lliyana Netseva8?. Stoyan Tomov® Petko Kashinski01ind co moy tocieQ Describe what you are looking for* {L.lliyana NetsevaNeRdd canvlliyana Netseva •nis converron s use oe weent ol va eseva aite vou, Cneck ounel oronle co leainimore diloouiaunetView Profilewtkas Kovalnk wxy pvЗЛЛАВОСКИМЯШІЛИ МИКИТКЕllivana Netseva 2-51 PMokolo 4 chasa svoboden li shte si ?wuKas KowalkepiryrСхимйно Мисля че НямакжЖ% вриьчухме се сьс Стоян и той беше запознат и по този кейс253 пол този кейс имам предвил httos://niminny.atlassian.net/browse/SRD-6881Jira Cloud +** prerieeh sal Transcription in save e° OpenReady for customera Lukas Kovalik (you)eottodwstDiXow RotmchSuenearвиж коментари и ако тоябва нещо друго може ла се чуемwiyana Neseva 2:5o PMvidgh kakvo si napisal, neshtoto koeto mi e stranno e che razlikata mejdu tova koeto pravi ta i tova koeto si napisal ti e natiskaneto na enter-a za da izpulni search-apopravi me ako greshaLukas Kovalik 2:57PMBiawiwenwnanaда се търси по transcriptlliyana Netseva 3.04 PMima li prichina tova da e napraveno po tozi nachin ili prosto taka e napraveno nqkogaukac Kowaik aciorНА ЭНАМ КОМПОЗИТЕН ПИЛТКЯ А МОЖА ВИ Т0968Я ЛАТСИ НАСТООМШІ НеЦІКТИТИ ПОСЛА ЛА НАСЛИВИШ АЛНЯ ЗА9ЯКА А СКАЛТИМ НаПЛИМетука не сьм сигурен. по-скоро е за поодукаMessage Iliyana Netseva...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85364
|
2922
|
12
|
2026-05-28T12:21:20.741402+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970880741_m1.jpg...
|
Slack
|
Iliyana Netseva (DM) - Jiminny Inc - 2 new items - Iliyana Netseva (DM) - Jiminny Inc - 2 new items - Slack...
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileEditViewNavigateCodeRefactorRunToolsGi PhpStormFileEditViewNavigateCodeRefactorRunToolsGitWindowHelpec2-user@ip-10-30-129-190:~881DEV (-zsh)O ₴2-zshN3screenpipe"84>0 llelX5A100% <8•Thu 28 May 15:21:20181ec2-user@ip-10-30-140-...₴7DOCKER#_####_#####\###1\#/V~'-[EMAIL] Linux 2023 (ECS Optimized)For documentation, visit [URL_WITH_CREDENTIALS] ~]$exitlogoutConnection to jiminny-prod-ecsi closed.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminnylukas@Lukas-Kovaliks-MacBook-Pro-JiminnyWarnine:Dormanentljiminny2APSai$ app~/J1minny/app(master) $ vprod(5025519)toist af known$1Ver31Vers3.11Version2023.11.20260413:Version2023.11!26296K27:Version2023.11.20260505:Version2023.11.20260509:Version2023.11.20260511:Version 2023.11.20260514:Run "/usr/bin/dnf check-release-update"for full release and version update info#_####_#####\\###||\#/Amazon Linux 2023 (ECS Optimized)Im/'For documentation, visit [URL_WITH_CREDENTIALS] ~]$ client_loop: send disconnect: Broken pipeukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-relay) $D...
|
NULL
|
8190314289129609
|
NULL
|
visual_change
|
ocr
|
NULL
|
PhpStormFileEditViewNavigateCodeRefactorRunToolsGi PhpStormFileEditViewNavigateCodeRefactorRunToolsGitWindowHelpec2-user@ip-10-30-129-190:~881DEV (-zsh)O ₴2-zshN3screenpipe"84>0 llelX5A100% <8•Thu 28 May 15:21:20181ec2-user@ip-10-30-140-...₴7DOCKER#_####_#####\###1\#/V~'-[EMAIL] Linux 2023 (ECS Optimized)For documentation, visit [URL_WITH_CREDENTIALS] ~]$exitlogoutConnection to jiminny-prod-ecsi closed.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminnylukas@Lukas-Kovaliks-MacBook-Pro-JiminnyWarnine:Dormanentljiminny2APSai$ app~/J1minny/app(master) $ vprod(5025519)toist af known$1Ver31Vers3.11Version2023.11.20260413:Version2023.11!26296K27:Version2023.11.20260505:Version2023.11.20260509:Version2023.11.20260511:Version 2023.11.20260514:Run "/usr/bin/dnf check-release-update"for full release and version update info#_####_#####\\###||\#/Amazon Linux 2023 (ECS Optimized)Im/'For documentation, visit [URL_WITH_CREDENTIALS] ~]$ client_loop: send disconnect: Broken pipeukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-relay) $D...
|
85363
|
NULL
|
NULL
|
NULL
|
|
85363
|
2922
|
11
|
2026-05-28T12:21:18.946386+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970878946_m1.jpg...
|
PhpStorm
|
faVsco.js – HubspotJournalPollingService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
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
45
1
41
66
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993
SELECT * FROM users WHERE id = 25061;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 994;
SELECT * FROM crm_profiles WHERE user_id = 25061;
select * from crm_configurations where id = 834;
SELECT * FROM teams WHERE id = 882;
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 = 882 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;
SELECT * FROM contacts where crm_configuration_id = 834;
SELECT * FROM opportunities WHERE team_id = 933
# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');
AND id IN (8482561,18352941,19042734,19232139,19445140,19472541);
SELECT * FROM opportunity_contacts
WHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 485; #
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
select crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id
where crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')
# and l.converted_at IS NOT NULL
;
# [PASSWORD_DOTS]
SELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')
and opportunity_id IS NULL
order by id desc;
SELECT * FROM teams WHERE id = 604; # 598
SELECT * FROM activities WHERE id = 74410828; # [EMAIL]
SELECT * FROM accounts WHERE id = 20068382;
SELECT * FROM accounts WHERE id = 35186038;
SELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;
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 = 559 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;
select * from sidekick_settings where team_id = 781;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100
SELECT * FROM crm_layouts WHERE crm_configuration_id = 711;
SELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL
and is_internal = 0 and status = 'completed'
order by id desc;
SELECT * FROM crm_layout_entities
WHERE crm_layout_id IN (2352, 2353);
;
SELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;
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 = 556 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;
SELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;
select * from contacts
where crm_configuration_id = 530
and crm_provider_id = 872252;
select * from activities where crm_configuration_id = 530
and user_id = 14343 and type like '%softphone%'
and created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);
SELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t
JOIN crm_configurations c ON t.id = c.team_id
WHERE t.status = 'active';
SELECT * FROM teams where id = 1091;
SELECT * FROM crm_configurations where team_id = 1091;
SELECT * FROM activity_providers where team_id = 1091;
SELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT * FROM teams WHERE name LIKE '%Leadventure%';
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 = 1091 and sa.provider = 'salesforce';
SELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812
SELECT * FROM teams where id = 862;
SELECT * FROM crm_configurations where team_id = 862;
SELECT * FROM activity_providers where team_id = 862;
SELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT t.id, crm.id, crm.provider, ap.* FROM teams t
join crm_configurations crm on t.id = crm.team_id
join activity_providers ap on t.id = ap.team_id
where t.status = 'active' and ap.is_enabled = 1
and crm.provider = 'hubspot'
and ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',
'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');
SELECT * FROM teams where id = 1068;
SELECT * FROM crm_configurations where team_id = 1068;
SELECT * FROM activity_providers where team_id = 1068;
SELECT * FROM activities a
where crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')
and a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'
)
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by a.id desc;
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 = 1068 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262
SELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;
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 = 882 and sa.provider = 'hubspot';
select * from crm_layouts where crm_configuration_id = 834;
select * from crm_layout_entities where crm_layout_id = 2780;
select * from crm_fields where id IN (321153,321192,321193,321194);
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
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 = 1057 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8
SELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20
SELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10
SELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #
SELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;
select * from users where team_id = 51; # 7783
SELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130
select * from activity_searches where user_id = 7783;
select * from activity_search_filters where activity_search_id IN (32291, 32292);
SELECT asf.activity_search_id, asf.id, asf.value
FROM activity_search_filters asf
WHERE asf.filter = 'group_id'
AND asf.value IN (
SELECT CONCAT(
HEX(SUBSTR(uuid, 5, 4)), '-',
HEX(SUBSTR(uuid, 3, 2)), '-',
HEX(SUBSTR(uuid, 1, 2)), '-',
HEX(SUBSTR(uuid, 9, 2)), '-',
HEX(SUBSTR(uuid, 11))
)
FROM groups
WHERE deleted_at IS NOT NULL
);
SELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where provider = 'hubspot';
SELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133
SELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null
# [PASSWORD_DOTS]
select * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';
select
cp.*
# DISTINCT t.id
# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields
FROM crm_profiles cp
JOIN crm_configurations crm on crm.id = cp.crm_configuration_id
JOIN users u on u.id = cp.user_id
JOIN teams t ON t.id = crm.team_id
WHERE crm.provider = 'salesforce' and t.status = 'active'
and cp.archived_at IS NULL and u.deleted_at IS NULL
and t.id NOT IN (1093)
and t.id = 2
and cp.contact_fields IS NULL;
# and c.crm_provider_id = '003Uu00000ojD4NIAU';
SELECT * FROM users WHERE id = 26484;
SELECT * FROM crm_profiles WHERE user_id = 26484;
SELECT * FROM social_accounts WHERE sociable_id = 26484;
SELECT * FROM crm_configurations where provider = 'salesforce';
select * from users where id IN (10022, 10403);
select * from users where team_id IN (526);
select * from teams where id IN (526, 532);
select * from crm_configurations where id IN (500, 516);
select * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);
select * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';
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 = 526 and sa.provider = 'salesforce';
select * from team_settings where team_id IN (526, 532);
select * from users where id IN (22824);
select * from crm_profiles where crm_configuration_id IN (1026);
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 = 1093 and sa.provider = 'salesforce';
select * from teams where id = 1099;
select * from users where id = 29643
select * from activity_processing_states;
SELECT * FROM teams where name LIKE '%Fare%'; # 233
SELECT * FROM opportunities where crm_configuration_id = 215
# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'
;
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 = 1088 and sa.provider = 'hubspot';
SELECT * FROM teams order by updated_at DESC
SELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account
select * from crm_configurations where provider = 'pipedrive';
select * from teams where id = 957;
select * from crm_configurations where id = 957;
SELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743
SELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;
select * from users where team_id = 1; # 26726 - Gabriela Dureva
SELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific
select * from activities where user_id = 26726 order by id desc;
select * from contacts where crm_configuration_id = 1
and email IN ('[EMAIL]', '[EMAIL]'); # 2094416, 2093620
SELECT * FROM contacts WHERE id = 6284931;
SELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id
WHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;
select * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);
select * from crm_configurations where id = 1;
43801692-1aeb-32ce-acba-5b80a479701a
44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b
405975c0-b3d0-7aaa-821f-09d59cae6dd1
4caf848d-4bed-2299-b248-7788d41f9fca
49bedc3f-f196-eef3-89c3-dea6a3b4aa63
43420989-a09d-b8f8-9806-c8bbf7a02aac
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 = 1 and sa.provider = 'salesforce';
SELECT * FROM activities WHERE id = 75461988;
SELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;
select * from contacts where id = 17900517;
select * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id
where crm.provider != 'salesforce';
select * from users where id = 21047;
SELECT * FROM crm_configurations WHERE id = 892;
SELECT * FROM teams WHERE id = 942;
select * from opportunities where team_id = 942 order by updated_at desc;
select * from contacts where team_id = 942 order by updated_at desc;
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 = 942 and sa.provider = 'hubspot';
SELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430
SELECT * FROM crm_configurations WHERE id = 1;
SELECT * FROM teams WHERE crm_id = 1;
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 = 1 and sa.provider = 'salesforce';
select id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1
SELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430
select * from teams where id = 852;
select * from groups where id = 2286;
select * from sidekick_settings where team_id = 852;
select * from default_activity_types where team_id = 852;
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1 AND u.deleted_at IS NULL
AND u.crm_required = 1
AND u.team_id = 1
ORDER BY u.team_id;
SELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (
18481
);
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1
AND u.deleted_at IS NULL
AND u.crm_required = 1
# AND u.team_id = 1
AND p.id IS NULL -- Move this condition to WHERE clause
ORDER BY u.team_id;
SELECT * FROM opportunities WHERE id = 20002609;
select * from teams where id = 1122; # Velatir, 29953 - [EMAIL]
select * from crm_configurations where id = 1060;
select * from crm_layouts where crm_configuration_id = 1060;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;
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 = 1122 and sa.provider = 'hubspot';
select * from opportunities where team_id = 1122 order by updated_at desc;
select * from crm_field_data where object_type = 'contact';
SELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262
SELECT
CONCAT(u.id, CASE WHEN u.id = t.own...
|
[{"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":"#12121 on JY-20963-fix-import-on-deleted-entity, menu","depth":5,"on_screen":true,"help_text":"Pull request #12121 exists for current branch JY-20963-fix-import-on-deleted-entity","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":"TextRelayServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","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":"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":"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":"AXStaticText","text":"12","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"9","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\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","role_description":"text entry area","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":"45","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"41","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"66","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":"SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993\nSELECT * FROM users WHERE id = 25061;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 994;\nSELECT * FROM crm_profiles WHERE user_id = 25061;\n\nselect * from crm_configurations where id = 834;\nSELECT * FROM teams WHERE id = 882;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;\n\nSELECT * FROM contacts where crm_configuration_id = 834;\nSELECT * FROM opportunities WHERE team_id = 933\n# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');\nAND id IN (8482561,18352941,19042734,19232139,19445140,19472541);\nSELECT * FROM opportunity_contacts\nWHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 485; #\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nselect crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id\nwhere crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')\n# and l.converted_at IS NOT NULL\n;\n\n# ********************************************************************\nSELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')\nand opportunity_id IS NULL\norder by id desc;\n\nSELECT * FROM teams WHERE id = 604; # 598\nSELECT * FROM activities WHERE id = 74410828; # chelseaw@allvoices.co\nSELECT * FROM accounts WHERE id = 20068382;\nSELECT * FROM accounts WHERE id = 35186038;\n\nSELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 559 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;\nselect * from sidekick_settings where team_id = 781;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100\n\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 711;\nSELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL\nand is_internal = 0 and status = 'completed'\norder by id desc;\n\nSELECT * FROM crm_layout_entities\nWHERE crm_layout_id IN (2352, 2353);\n;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 556 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;\nSELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;\nselect * from contacts\nwhere crm_configuration_id = 530\nand crm_provider_id = 872252;\n\nselect * from activities where crm_configuration_id = 530\nand user_id = 14343 and type like '%softphone%'\nand created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);\n\n\nSELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t\nJOIN crm_configurations c ON t.id = c.team_id\nWHERE t.status = 'active';\n\nSELECT * FROM teams where id = 1091;\nSELECT * FROM crm_configurations where team_id = 1091;\nSELECT * FROM activity_providers where team_id = 1091;\nSELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT * FROM teams WHERE name LIKE '%Leadventure%';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1091 and sa.provider = 'salesforce';\n\nSELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812\nSELECT * FROM teams where id = 862;\nSELECT * FROM crm_configurations where team_id = 862;\nSELECT * FROM activity_providers where team_id = 862;\nSELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT t.id, crm.id, crm.provider, ap.* FROM teams t\njoin crm_configurations crm on t.id = crm.team_id\njoin activity_providers ap on t.id = ap.team_id\nwhere t.status = 'active' and ap.is_enabled = 1\nand crm.provider = 'hubspot'\nand ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',\n 'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');\n\nSELECT * FROM teams where id = 1068;\nSELECT * FROM crm_configurations where team_id = 1068;\nSELECT * FROM activity_providers where team_id = 1068;\n\nSELECT * FROM activities a\nwhere crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')\nand a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'\n )\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by a.id desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1068 and sa.provider = 'hubspot';\n\n# ********************************************************************\n# ********************************************************************\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262\nSELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\nselect * from crm_layouts where crm_configuration_id = 834;\nselect * from crm_layout_entities where crm_layout_id = 2780;\nselect * from crm_fields where id IN (321153,321192,321193,321194);\n\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1057 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8\n\nSELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20\n\nSELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10\n\nSELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #\n\nSELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;\nselect * from users where team_id = 51; # 7783\nSELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130\nselect * from activity_searches where user_id = 7783;\nselect * from activity_search_filters where activity_search_id IN (32291, 32292);\n\nSELECT asf.activity_search_id, asf.id, asf.value\nFROM activity_search_filters asf\nWHERE asf.filter = 'group_id'\nAND asf.value IN (\n SELECT CONCAT(\n HEX(SUBSTR(uuid, 5, 4)), '-',\n HEX(SUBSTR(uuid, 3, 2)), '-',\n HEX(SUBSTR(uuid, 1, 2)), '-',\n HEX(SUBSTR(uuid, 9, 2)), '-',\n HEX(SUBSTR(uuid, 11))\n )\n FROM groups\n WHERE deleted_at IS NOT NULL\n);\n\nSELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th\n# ********************************************************************\nSELECT * FROM crm_configurations where provider = 'hubspot';\nSELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133\nSELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null\n# ********************************************************************\n\nselect * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';\nselect\n cp.*\n# DISTINCT t.id\n# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields\nFROM crm_profiles cp\nJOIN crm_configurations crm on crm.id = cp.crm_configuration_id\nJOIN users u on u.id = cp.user_id\nJOIN teams t ON t.id = crm.team_id\nWHERE crm.provider = 'salesforce' and t.status = 'active'\n and cp.archived_at IS NULL and u.deleted_at IS NULL\n and t.id NOT IN (1093)\n and t.id = 2\n and cp.contact_fields IS NULL;\n# and c.crm_provider_id = '003Uu00000ojD4NIAU';\n\nSELECT * FROM users WHERE id = 26484;\nSELECT * FROM crm_profiles WHERE user_id = 26484;\nSELECT * FROM social_accounts WHERE sociable_id = 26484;\nSELECT * FROM crm_configurations where provider = 'salesforce';\nselect * from users where id IN (10022, 10403);\nselect * from users where team_id IN (526);\nselect * from teams where id IN (526, 532);\nselect * from crm_configurations where id IN (500, 516);\nselect * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);\nselect * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 526 and sa.provider = 'salesforce';\nselect * from team_settings where team_id IN (526, 532);\n\nselect * from users where id IN (22824);\nselect * from crm_profiles where crm_configuration_id IN (1026);\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1093 and sa.provider = 'salesforce';\n\nselect * from teams where id = 1099;\nselect * from users where id = 29643\n\nselect * from activity_processing_states;\n\nSELECT * FROM teams where name LIKE '%Fare%'; # 233\nSELECT * FROM opportunities where crm_configuration_id = 215\n# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1088 and sa.provider = 'hubspot';\n\nSELECT * FROM teams order by updated_at DESC\nSELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account\n\nselect * from crm_configurations where provider = 'pipedrive';\n\nselect * from teams where id = 957;\nselect * from crm_configurations where id = 957;\n\nSELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743\nSELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;\n\nselect * from users where team_id = 1; # 26726 - Gabriela Dureva\nSELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific\nselect * from activities where user_id = 26726 order by id desc;\nselect * from contacts where crm_configuration_id = 1\nand email IN ('charlotte.ward@prolific.com', 'frankie.bryant@prolific.com'); # 2094416, 2093620\nSELECT * FROM contacts WHERE id = 6284931;\n\nSELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id\nWHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;\n\nselect * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);\nselect * from crm_configurations where id = 1;\n\n43801692-1aeb-32ce-acba-5b80a479701a\n44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b\n405975c0-b3d0-7aaa-821f-09d59cae6dd1\n4caf848d-4bed-2299-b248-7788d41f9fca\n49bedc3f-f196-eef3-89c3-dea6a3b4aa63\n43420989-a09d-b8f8-9806-c8bbf7a02aac\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nSELECT * FROM activities WHERE id = 75461988;\n\nSELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;\n\nselect * from contacts where id = 17900517;\n\nselect * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id\nwhere crm.provider != 'salesforce';\n\nselect * from users where id = 21047;\nSELECT * FROM crm_configurations WHERE id = 892;\nSELECT * FROM teams WHERE id = 942;\nselect * from opportunities where team_id = 942 order by updated_at desc;\nselect * from contacts where team_id = 942 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 942 and sa.provider = 'hubspot';\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430\nSELECT * FROM crm_configurations WHERE id = 1;\nSELECT * FROM teams WHERE crm_id = 1;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nselect id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1\nSELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430\n\nselect * from teams where id = 852;\nselect * from groups where id = 2286;\nselect * from sidekick_settings where team_id = 852;\nselect * from default_activity_types where team_id = 852;\n\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1 AND u.deleted_at IS NULL\nAND u.crm_required = 1\nAND u.team_id = 1\nORDER BY u.team_id;\n\nSELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (\n18481\n );\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1\n AND u.deleted_at IS NULL\n AND u.crm_required = 1\n# AND u.team_id = 1\n AND p.id IS NULL -- Move this condition to WHERE clause\nORDER BY u.team_id;\n\nSELECT * FROM opportunities WHERE id = 20002609;\nselect * from teams where id = 1122; # Velatir, 29953 - christian@velatir.com\nselect * from crm_configurations where id = 1060;\nselect * from crm_layouts where crm_configuration_id = 1060;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1122 and sa.provider = 'hubspot';\nselect * from opportunities where team_id = 1122 order by updated_at desc;\n\nselect * from crm_field_data where object_type = 'contact';\n\nSELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 248 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS\nSELECT * FROM users where id = 24115;\nSELECT * FROM accounts where id = 4002896;\nSELECT * FROM teams WHERE name LIKE '%adswerve%';\nSELECT * FROM opportunities where crm_configuration_id = 230 AND crm_provider_id IN (\"0069N000003GIQ9QAO\",\"0061r000019yGP9AAM\",\"0066900001S2KWlAAN\",\"0066900001TDpj2AAD\",\"0066900001b8uEwAAI\",\"0069N000001rQi0QAE\",\"006QF00000KD40mYAD\",\"006QF00000LzpRJYAZ\",\"0069N000002uomtQAA\",\"0069N000002xlMLQAY\",\"0066900001NV6ubAAD\",\"0061r00001HJp45AAD\",\"006QF00000uTlUoYAK\",\"006QF00000v0bZqYAI\");\nSELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203\n\nSELECT u.id, u.email, ac.name, a.* FROM activities a\nJOIN users u ON a.user_id = u.id\nJOIN accounts ac ON a.account_id = ac.id\nWHERE\nuuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or\nuuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or\nuuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;\n\nselect * from users where id = 5825;\nSELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;\n\nselect * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;\n19594, 862\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 862 and sa.provider = 'salesforce';\n\nselect * from automated_reports where id = 36;\nselect ar.frequency, r.*, ar.* from automated_report_results r\njoin automated_reports ar on r.report_id = ar.id\nwhere ar.frequency != 'one_off';\n\nselect s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;\nselect * from nudges n where n.activity_search_id\n\nselect * from teams where created_at > '2026-03-09';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;\n\nselect * from users where team_id = 1 and name like '%Lukas%'; # 7160\n\nSELECT * FROM teams WHERE id = 575;\nselect * from opportunities where team_id = 575;\nSELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,\nselect * from opportunities where team_id = 1126;\nSELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,\nselect * from opportunities where team_id = 1125;\nselect * from contacts c\nwhere c.team_id = 882;\n\nSELECT * FROM activities WHERE id = 76822967;\nSELECT * FROM crm_profiles WHERE user_id = 15440;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 555;\nSELECT * FROM crm_configurations WHERE id = 555;\nSELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 581 and sa.provider = 'salesforce';\n\nSELECT * FROM automated_report_results order by id desc;\n\nselect * from features;\nselect * from team_features where feature_id = 40;\n\nselect * from teams where id = 556;\n\nselect * from automated_reports;\nwhere id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , [\"pdf\",\"podcast\"]\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\nselect * from automated_report_results order by id desc;\nSELECT * FROM automated_report_results WHERE id = 1919;\n\nselect * from automated_report_results WHERE report_id = 54;\n\nselect * from opportunities where id = 7594349;\n\nSELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067 - jiminnyintegration@lesmills.com\nselect * from playbooks where team_id = 711; # event 226147\nSELECT * FROM playbook_categories WHERE playbook_id = 5515;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event';\nSELECT * FROM crm_fields WHERE id = 226147;\nSELECT * FROM crm_field_values WHERE crm_field_id = 226147;\n\nSELECT * FROM crm_configurations WHERE id = 692;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 711 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles cp JOIN users u on u.id = cp.user_id WHERE u.team_id = 711;\n\nselect * from leads;\n\nselect * from calendars;\n\nSELECT\n t.id AS team_id,\n t.name,\n LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1)) AS calendar_domain\nFROM teams t\nJOIN users u ON u.team_id = t.id\nJOIN calendars c ON c.user_id = u.id AND c.status = 'active' AND c.calendar_provider_id LIKE '%@%'\nLEFT JOIN team_domains td\n ON td.team_id = t.id\n AND td.deleted_at IS NULL\n AND td.domain = LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1))\nGROUP BY t.id, t.name, calendar_domain\nORDER BY t.name, calendar_domain;\n\nselect * from users u join calendars c on c.user_id = u.id\nwhere u.team_id = 882;\n\n\nselect * from activities where id = 74049485; # team 563 crm 537\nselect * from activities where id = 73272382; # team 563 crm 537\nselect * from activities where id = 64400389; # team 563 crm 537\nselect * from activities where id = 58081273; # team 563 crm 537\nselect * from activities where id = 54520297; # team 563 crm 537\nselect * from participants where activity_id = 58081273;\n\nselect * from activities where crm_configuration_id = 537 and provider = 'aircall'\nand account_id = 19003658 order by updated_at desc;\n\nselect * from contacts where crm_configuration_id = 537 and id = 35957759;\nselect * from accounts where crm_configuration_id = 537 and id = 19003658;\n\nselect * from automated_report_results where id = 1976;\nselect * from automated_reports where id = 583;\nselect * from activity_searches where id = 87714;\nselect * from activity_search_filters where activity_search_id = 87714;\n\nSELECT * FROM activities WHERE uuid_to_bin('8827f672-202d-4162-9d04-73ff5f0566a9') = uuid\nor uuid_to_bin('47842446-af51-4bcb-854f-cc6560290101') = uuid;\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot';\nselect * from rate_limits;\n\nselect * from automated_report_results where media_type = 'pdf' and status = 2\nand id IN (18, 1872);\nselect * from automated_reports where id = 54;\nSELECT * FROM users WHERE id IN (24623,29443,29613);\n\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\n\nselect * from text_relays where created_at > '2026-05-01';\nand id IN (32415, 32416);\n# and id = 32412;\n\nselect * from users where team_id = 2 and email like '%scott%' and id = 29510;\n\nSELECT * FROM activities WHERE uuid_to_bin('67cebfc2-ed56-44a2-8c68-7a0286ed8618') = uuid; # 79763436\n\nSELECT email_provider_id, COUNT(*) as count, GROUP_CONCAT(id) as ids, GROUP_CONCAT(status) as statuses\nFROM text_relays\nWHERE email_provider_id IN ('19e2027868a64b42', '19e2033ed8ea6b10')\nGROUP BY email_provider_id;\nSELECT id, status, telephony_provider_id, created_at\nFROM activities\nWHERE id IN (80028719, 80028846);\nSELECT id, status, code, email_sent_at, created_at, updated_at\nFROM text_relays\nWHERE id IN (32415, 32416);\nSELECT id, status, code, sender, recipient, created_at\nFROM text_relays\nWHERE sender LIKE '%mario.georgiev%' OR sender LIKE '%stoyan.tomov%'\nORDER BY created_at DESC\nLIMIT 10;\n\nSELECT id, uuid, status, code, sender, recipient, created_at, updated_at\nFROM text_relays\nWHERE uuid = uuid_to_bin('0626141c-27a6-4d8c-aff8-7c8020a2c656');\n\n# ***************\nSELECT DISTINCT u.id, u.email, u.name, u.softphone_number, COUNT(a.id) as sms_count\nFROM users u\nINNER JOIN activities a ON u.id = .user_id\nWHERE a.type LIKE 'sms%'\nAND a.created_at > DATE_SUB(NOW(), INTERVAL 30 DAY)\nGROUP BY u.id, u.email, u.name, u.softphone_number\nORDER BY sms_count DESC;\n\nselect * from teams where id = 1;\n\nselect * from roles;\n\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1117 and sa.provider = 'hubspot';\nSELECT * FROM activities WHERE uuid_to_bin('8024fffb-2df7-4017-91f4-d9f896050248') = uuid; # 79933459 YES\nSELECT * FROM activities WHERE uuid_to_bin('25529043-8094-4781-927f-4f4da2a8185c') = uuid; # 80186192 NO\nSELECT * FROM crm_configurations WHERE id = 1053;\nSELECT * FROM teams WHERE id = 1117;\nselect * from users where id = 30249;\nselect * from playbooks where id = 5473;\nselect * from playbook_categories where id = 43783;\nselect * from playbook_categories where playbook_id = 5473;\nselect * from crm_fields where id = 659242;\nselect * from crm_field_values where crm_field_id = 659242;\n\nSELECT * FROM crm_field_data fd\n# JOIN crm_fields f ON fd.crm_field_id = f.id\n# JOIN activities a ON fd.activity_id = a.id\nWHERE activity_id = 79933459\n# AND f.crm_provider_id = 'hs_activity_type';\n\n\nSELECT * FROM activity_messages;\nselect * from text_relays where created_at > '2026-05-01';\nselect * from activities where user_id IN (7160, 18608) and created_at > '2026-05-22' order by id desc;\nselect * from users where team_id = 1 and id IN (18608, 13934, 7160);\nselect * from activities where user_id = 7160 order by id desc limit 10;\n\nselect * from accounts where team_id = 1 and name = 'Column5';\n\nselect * from users where name like '%Subra%'; # 31054, 1117\nselect * from teams where id = 1117;\nselect * from activity_searches where user_id = 31054;\nselect * from activity_search_filters where activity_search_id IN (88882, 88902);","depth":4,"on_screen":true,"value":"SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993\nSELECT * FROM users WHERE id = 25061;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 994;\nSELECT * FROM crm_profiles WHERE user_id = 25061;\n\nselect * from crm_configurations where id = 834;\nSELECT * FROM teams WHERE id = 882;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;\n\nSELECT * FROM contacts where crm_configuration_id = 834;\nSELECT * FROM opportunities WHERE team_id = 933\n# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');\nAND id IN (8482561,18352941,19042734,19232139,19445140,19472541);\nSELECT * FROM opportunity_contacts\nWHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 485; #\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nselect crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id\nwhere crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')\n# and l.converted_at IS NOT NULL\n;\n\n# ********************************************************************\nSELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')\nand opportunity_id IS NULL\norder by id desc;\n\nSELECT * FROM teams WHERE id = 604; # 598\nSELECT * FROM activities WHERE id = 74410828; # chelseaw@allvoices.co\nSELECT * FROM accounts WHERE id = 20068382;\nSELECT * FROM accounts WHERE id = 35186038;\n\nSELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 559 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;\nselect * from sidekick_settings where team_id = 781;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100\n\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 711;\nSELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL\nand is_internal = 0 and status = 'completed'\norder by id desc;\n\nSELECT * FROM crm_layout_entities\nWHERE crm_layout_id IN (2352, 2353);\n;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 556 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;\nSELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;\nselect * from contacts\nwhere crm_configuration_id = 530\nand crm_provider_id = 872252;\n\nselect * from activities where crm_configuration_id = 530\nand user_id = 14343 and type like '%softphone%'\nand created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);\n\n\nSELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t\nJOIN crm_configurations c ON t.id = c.team_id\nWHERE t.status = 'active';\n\nSELECT * FROM teams where id = 1091;\nSELECT * FROM crm_configurations where team_id = 1091;\nSELECT * FROM activity_providers where team_id = 1091;\nSELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT * FROM teams WHERE name LIKE '%Leadventure%';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1091 and sa.provider = 'salesforce';\n\nSELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812\nSELECT * FROM teams where id = 862;\nSELECT * FROM crm_configurations where team_id = 862;\nSELECT * FROM activity_providers where team_id = 862;\nSELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT t.id, crm.id, crm.provider, ap.* FROM teams t\njoin crm_configurations crm on t.id = crm.team_id\njoin activity_providers ap on t.id = ap.team_id\nwhere t.status = 'active' and ap.is_enabled = 1\nand crm.provider = 'hubspot'\nand ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',\n 'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');\n\nSELECT * FROM teams where id = 1068;\nSELECT * FROM crm_configurations where team_id = 1068;\nSELECT * FROM activity_providers where team_id = 1068;\n\nSELECT * FROM activities a\nwhere crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')\nand a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'\n )\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by a.id desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1068 and sa.provider = 'hubspot';\n\n# ********************************************************************\n# ********************************************************************\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262\nSELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\nselect * from crm_layouts where crm_configuration_id = 834;\nselect * from crm_layout_entities where crm_layout_id = 2780;\nselect * from crm_fields where id IN (321153,321192,321193,321194);\n\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1057 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8\n\nSELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20\n\nSELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10\n\nSELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #\n\nSELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;\nselect * from users where team_id = 51; # 7783\nSELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130\nselect * from activity_searches where user_id = 7783;\nselect * from activity_search_filters where activity_search_id IN (32291, 32292);\n\nSELECT asf.activity_search_id, asf.id, asf.value\nFROM activity_search_filters asf\nWHERE asf.filter = 'group_id'\nAND asf.value IN (\n SELECT CONCAT(\n HEX(SUBSTR(uuid, 5, 4)), '-',\n HEX(SUBSTR(uuid, 3, 2)), '-',\n HEX(SUBSTR(uuid, 1, 2)), '-',\n HEX(SUBSTR(uuid, 9, 2)), '-',\n HEX(SUBSTR(uuid, 11))\n )\n FROM groups\n WHERE deleted_at IS NOT NULL\n);\n\nSELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th\n# ********************************************************************\nSELECT * FROM crm_configurations where provider = 'hubspot';\nSELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133\nSELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null\n# ********************************************************************\n\nselect * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';\nselect\n cp.*\n# DISTINCT t.id\n# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields\nFROM crm_profiles cp\nJOIN crm_configurations crm on crm.id = cp.crm_configuration_id\nJOIN users u on u.id = cp.user_id\nJOIN teams t ON t.id = crm.team_id\nWHERE crm.provider = 'salesforce' and t.status = 'active'\n and cp.archived_at IS NULL and u.deleted_at IS NULL\n and t.id NOT IN (1093)\n and t.id = 2\n and cp.contact_fields IS NULL;\n# and c.crm_provider_id = '003Uu00000ojD4NIAU';\n\nSELECT * FROM users WHERE id = 26484;\nSELECT * FROM crm_profiles WHERE user_id = 26484;\nSELECT * FROM social_accounts WHERE sociable_id = 26484;\nSELECT * FROM crm_configurations where provider = 'salesforce';\nselect * from users where id IN (10022, 10403);\nselect * from users where team_id IN (526);\nselect * from teams where id IN (526, 532);\nselect * from crm_configurations where id IN (500, 516);\nselect * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);\nselect * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 526 and sa.provider = 'salesforce';\nselect * from team_settings where team_id IN (526, 532);\n\nselect * from users where id IN (22824);\nselect * from crm_profiles where crm_configuration_id IN (1026);\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1093 and sa.provider = 'salesforce';\n\nselect * from teams where id = 1099;\nselect * from users where id = 29643\n\nselect * from activity_processing_states;\n\nSELECT * FROM teams where name LIKE '%Fare%'; # 233\nSELECT * FROM opportunities where crm_configuration_id = 215\n# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1088 and sa.provider = 'hubspot';\n\nSELECT * FROM teams order by updated_at DESC\nSELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account\n\nselect * from crm_configurations where provider = 'pipedrive';\n\nselect * from teams where id = 957;\nselect * from crm_configurations where id = 957;\n\nSELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743\nSELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;\n\nselect * from users where team_id = 1; # 26726 - Gabriela Dureva\nSELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific\nselect * from activities where user_id = 26726 order by id desc;\nselect * from contacts where crm_configuration_id = 1\nand email IN ('charlotte.ward@prolific.com', 'frankie.bryant@prolific.com'); # 2094416, 2093620\nSELECT * FROM contacts WHERE id = 6284931;\n\nSELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id\nWHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;\n\nselect * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);\nselect * from crm_configurations where id = 1;\n\n43801692-1aeb-32ce-acba-5b80a479701a\n44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b\n405975c0-b3d0-7aaa-821f-09d59cae6dd1\n4caf848d-4bed-2299-b248-7788d41f9fca\n49bedc3f-f196-eef3-89c3-dea6a3b4aa63\n43420989-a09d-b8f8-9806-c8bbf7a02aac\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nSELECT * FROM activities WHERE id = 75461988;\n\nSELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;\n\nselect * from contacts where id = 17900517;\n\nselect * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id\nwhere crm.provider != 'salesforce';\n\nselect * from users where id = 21047;\nSELECT * FROM crm_configurations WHERE id = 892;\nSELECT * FROM teams WHERE id = 942;\nselect * from opportunities where team_id = 942 order by updated_at desc;\nselect * from contacts where team_id = 942 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 942 and sa.provider = 'hubspot';\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430\nSELECT * FROM crm_configurations WHERE id = 1;\nSELECT * FROM teams WHERE crm_id = 1;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nselect id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1\nSELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430\n\nselect * from teams where id = 852;\nselect * from groups where id = 2286;\nselect * from sidekick_settings where team_id = 852;\nselect * from default_activity_types where team_id = 852;\n\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1 AND u.deleted_at IS NULL\nAND u.crm_required = 1\nAND u.team_id = 1\nORDER BY u.team_id;\n\nSELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (\n18481\n );\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1\n AND u.deleted_at IS NULL\n AND u.crm_required = 1\n# AND u.team_id = 1\n AND p.id IS NULL -- Move this condition to WHERE clause\nORDER BY u.team_id;\n\nSELECT * FROM opportunities WHERE id = 20002609;\nselect * from teams where id = 1122; # Velatir, 29953 - christian@velatir.com\nselect * from crm_configurations where id = 1060;\nselect * from crm_layouts where crm_configuration_id = 1060;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1122 and sa.provider = 'hubspot';\nselect * from opportunities where team_id = 1122 order by updated_at desc;\n\nselect * from crm_field_data where object_type = 'contact';\n\nSELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 248 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS\nSELECT * FROM users where id = 24115;\nSELECT * FROM accounts where id = 4002896;\nSELECT * FROM teams WHERE name LIKE '%adswerve%';\nSELECT * FROM opportunities where crm_configuration_id = 230 AND crm_provider_id IN (\"0069N000003GIQ9QAO\",\"0061r000019yGP9AAM\",\"0066900001S2KWlAAN\",\"0066900001TDpj2AAD\",\"0066900001b8uEwAAI\",\"0069N000001rQi0QAE\",\"006QF00000KD40mYAD\",\"006QF00000LzpRJYAZ\",\"0069N000002uomtQAA\",\"0069N000002xlMLQAY\",\"0066900001NV6ubAAD\",\"0061r00001HJp45AAD\",\"006QF00000uTlUoYAK\",\"006QF00000v0bZqYAI\");\nSELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203\n\nSELECT u.id, u.email, ac.name, a.* FROM activities a\nJOIN users u ON a.user_id = u.id\nJOIN accounts ac ON a.account_id = ac.id\nWHERE\nuuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or\nuuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or\nuuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;\n\nselect * from users where id = 5825;\nSELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;\n\nselect * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;\n19594, 862\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 862 and sa.provider = 'salesforce';\n\nselect * from automated_reports where id = 36;\nselect ar.frequency, r.*, ar.* from automated_report_results r\njoin automated_reports ar on r.report_id = ar.id\nwhere ar.frequency != 'one_off';\n\nselect s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;\nselect * from nudges n where n.activity_search_id\n\nselect * from teams where created_at > '2026-03-09';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;\n\nselect * from users where team_id = 1 and name like '%Lukas%'; # 7160\n\nSELECT * FROM teams WHERE id = 575;\nselect * from opportunities where team_id = 575;\nSELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,\nselect * from opportunities where team_id = 1126;\nSELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,\nselect * from opportunities where team_id = 1125;\nselect * from contacts c\nwhere c.team_id = 882;\n\nSELECT * FROM activities WHERE id = 76822967;\nSELECT * FROM crm_profiles WHERE user_id = 15440;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 555;\nSELECT * FROM crm_configurations WHERE id = 555;\nSELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 581 and sa.provider = 'salesforce';\n\nSELECT * FROM automated_report_results order by id desc;\n\nselect * from features;\nselect * from team_features where feature_id = 40;\n\nselect * from teams where id = 556;\n\nselect * from automated_reports;\nwhere id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , [\"pdf\",\"podcast\"]\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\nselect * from automated_report_results order by id desc;\nSELECT * FROM automated_report_results WHERE id = 1919;\n\nselect * from automated_report_results WHERE report_id = 54;\n\nselect * from opportunities where id = 7594349;\n\nSELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067 - jiminnyintegration@lesmills.com\nselect * from playbooks where team_id = 711; # event 226147\nSELECT * FROM playbook_categories WHERE playbook_id = 5515;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event';\nSELECT * FROM crm_fields WHERE id = 226147;\nSELECT * FROM crm_field_values WHERE crm_field_id = 226147;\n\nSELECT * FROM crm_configurations WHERE id = 692;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 711 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles cp JOIN users u on u.id = cp.user_id WHERE u.team_id = 711;\n\nselect * from leads;\n\nselect * from calendars;\n\nSELECT\n t.id AS team_id,\n t.name,\n LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1)) AS calendar_domain\nFROM teams t\nJOIN users u ON u.team_id = t.id\nJOIN calendars c ON c.user_id = u.id AND c.status = 'active' AND c.calendar_provider_id LIKE '%@%'\nLEFT JOIN team_domains td\n ON td.team_id = t.id\n AND td.deleted_at IS NULL\n AND td.domain = LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1))\nGROUP BY t.id, t.name, calendar_domain\nORDER BY t.name, calendar_domain;\n\nselect * from users u join calendars c on c.user_id = u.id\nwhere u.team_id = 882;\n\n\nselect * from activities where id = 74049485; # team 563 crm 537\nselect * from activities where id = 73272382; # team 563 crm 537\nselect * from activities where id = 64400389; # team 563 crm 537\nselect * from activities where id = 58081273; # team 563 crm 537\nselect * from activities where id = 54520297; # team 563 crm 537\nselect * from participants where activity_id = 58081273;\n\nselect * from activities where crm_configuration_id = 537 and provider = 'aircall'\nand account_id = 19003658 order by updated_at desc;\n\nselect * from contacts where crm_configuration_id = 537 and id = 35957759;\nselect * from accounts where crm_configuration_id = 537 and id = 19003658;\n\nselect * from automated_report_results where id = 1976;\nselect * from automated_reports where id = 583;\nselect * from activity_searches where id = 87714;\nselect * from activity_search_filters where activity_search_id = 87714;\n\nSELECT * FROM activities WHERE uuid_to_bin('8827f672-202d-4162-9d04-73ff5f0566a9') = uuid\nor uuid_to_bin('47842446-af51-4bcb-854f-cc6560290101') = uuid;\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot';\nselect * from rate_limits;\n\nselect * from automated_report_results where media_type = 'pdf' and status = 2\nand id IN (18, 1872);\nselect * from automated_reports where id = 54;\nSELECT * FROM users WHERE id IN (24623,29443,29613);\n\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\n\nselect * from text_relays where created_at > '2026-05-01';\nand id IN (32415, 32416);\n# and id = 32412;\n\nselect * from users where team_id = 2 and email like '%scott%' and id = 29510;\n\nSELECT * FROM activities WHERE uuid_to_bin('67cebfc2-ed56-44a2-8c68-7a0286ed8618') = uuid; # 79763436\n\nSELECT email_provider_id, COUNT(*) as count, GROUP_CONCAT(id) as ids, GROUP_CONCAT(status) as statuses\nFROM text_relays\nWHERE email_provider_id IN ('19e2027868a64b42', '19e2033ed8ea6b10')\nGROUP BY email_provider_id;\nSELECT id, status, telephony_provider_id, created_at\nFROM activities\nWHERE id IN (80028719, 80028846);\nSELECT id, status, code, email_sent_at, created_at, updated_at\nFROM text_relays\nWHERE id IN (32415, 32416);\nSELECT id, status, code, sender, recipient, created_at\nFROM text_relays\nWHERE sender LIKE '%mario.georgiev%' OR sender LIKE '%stoyan.tomov%'\nORDER BY created_at DESC\nLIMIT 10;\n\nSELECT id, uuid, status, code, sender, recipient, created_at, updated_at\nFROM text_relays\nWHERE uuid = uuid_to_bin('0626141c-27a6-4d8c-aff8-7c8020a2c656');\n\n# ***************\nSELECT DISTINCT u.id, u.email, u.name, u.softphone_number, COUNT(a.id) as sms_count\nFROM users u\nINNER JOIN activities a ON u.id = .user_id\nWHERE a.type LIKE 'sms%'\nAND a.created_at > DATE_SUB(NOW(), INTERVAL 30 DAY)\nGROUP BY u.id, u.email, u.name, u.softphone_number\nORDER BY sms_count DESC;\n\nselect * from teams where id = 1;\n\nselect * from roles;\n\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1117 and sa.provider = 'hubspot';\nSELECT * FROM activities WHERE uuid_to_bin('8024fffb-2df7-4017-91f4-d9f896050248') = uuid; # 79933459 YES\nSELECT * FROM activities WHERE uuid_to_bin('25529043-8094-4781-927f-4f4da2a8185c') = uuid; # 80186192 NO\nSELECT * FROM crm_configurations WHERE id = 1053;\nSELECT * FROM teams WHERE id = 1117;\nselect * from users where id = 30249;\nselect * from playbooks where id = 5473;\nselect * from playbook_categories where id = 43783;\nselect * from playbook_categories where playbook_id = 5473;\nselect * from crm_fields where id = 659242;\nselect * from crm_field_values where crm_field_id = 659242;\n\nSELECT * FROM crm_field_data fd\n# JOIN crm_fields f ON fd.crm_field_id = f.id\n# JOIN activities a ON fd.activity_id = a.id\nWHERE activity_id = 79933459\n# AND f.crm_provider_id = 'hs_activity_type';\n\n\nSELECT * FROM activity_messages;\nselect * from text_relays where created_at > '2026-05-01';\nselect * from activities where user_id IN (7160, 18608) and created_at > '2026-05-22' order by id desc;\nselect * from users where team_id = 1 and id IN (18608, 13934, 7160);\nselect * from activities where user_id = 7160 order by id desc limit 10;\n\nselect * from accounts where team_id = 1 and name = 'Column5';\n\nselect * from users where name like '%Subra%'; # 31054, 1117\nselect * from teams where id = 1117;\nselect * from activity_searches where user_id = 31054;\nselect * from activity_search_filters where activity_search_id IN (88882, 88902);","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":"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}]...
|
2526630547225287665
|
1137771414947247693
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
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
45
1
41
66
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993
SELECT * FROM users WHERE id = 25061;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 994;
SELECT * FROM crm_profiles WHERE user_id = 25061;
select * from crm_configurations where id = 834;
SELECT * FROM teams WHERE id = 882;
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 = 882 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;
SELECT * FROM contacts where crm_configuration_id = 834;
SELECT * FROM opportunities WHERE team_id = 933
# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');
AND id IN (8482561,18352941,19042734,19232139,19445140,19472541);
SELECT * FROM opportunity_contacts
WHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 485; #
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
select crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id
where crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')
# and l.converted_at IS NOT NULL
;
# [PASSWORD_DOTS]
SELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')
and opportunity_id IS NULL
order by id desc;
SELECT * FROM teams WHERE id = 604; # 598
SELECT * FROM activities WHERE id = 74410828; # [EMAIL]
SELECT * FROM accounts WHERE id = 20068382;
SELECT * FROM accounts WHERE id = 35186038;
SELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;
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 = 559 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;
select * from sidekick_settings where team_id = 781;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100
SELECT * FROM crm_layouts WHERE crm_configuration_id = 711;
SELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL
and is_internal = 0 and status = 'completed'
order by id desc;
SELECT * FROM crm_layout_entities
WHERE crm_layout_id IN (2352, 2353);
;
SELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;
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 = 556 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;
SELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;
select * from contacts
where crm_configuration_id = 530
and crm_provider_id = 872252;
select * from activities where crm_configuration_id = 530
and user_id = 14343 and type like '%softphone%'
and created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);
SELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t
JOIN crm_configurations c ON t.id = c.team_id
WHERE t.status = 'active';
SELECT * FROM teams where id = 1091;
SELECT * FROM crm_configurations where team_id = 1091;
SELECT * FROM activity_providers where team_id = 1091;
SELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT * FROM teams WHERE name LIKE '%Leadventure%';
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 = 1091 and sa.provider = 'salesforce';
SELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812
SELECT * FROM teams where id = 862;
SELECT * FROM crm_configurations where team_id = 862;
SELECT * FROM activity_providers where team_id = 862;
SELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT t.id, crm.id, crm.provider, ap.* FROM teams t
join crm_configurations crm on t.id = crm.team_id
join activity_providers ap on t.id = ap.team_id
where t.status = 'active' and ap.is_enabled = 1
and crm.provider = 'hubspot'
and ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',
'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');
SELECT * FROM teams where id = 1068;
SELECT * FROM crm_configurations where team_id = 1068;
SELECT * FROM activity_providers where team_id = 1068;
SELECT * FROM activities a
where crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')
and a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'
)
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by a.id desc;
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 = 1068 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262
SELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;
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 = 882 and sa.provider = 'hubspot';
select * from crm_layouts where crm_configuration_id = 834;
select * from crm_layout_entities where crm_layout_id = 2780;
select * from crm_fields where id IN (321153,321192,321193,321194);
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
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 = 1057 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8
SELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20
SELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10
SELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #
SELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;
select * from users where team_id = 51; # 7783
SELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130
select * from activity_searches where user_id = 7783;
select * from activity_search_filters where activity_search_id IN (32291, 32292);
SELECT asf.activity_search_id, asf.id, asf.value
FROM activity_search_filters asf
WHERE asf.filter = 'group_id'
AND asf.value IN (
SELECT CONCAT(
HEX(SUBSTR(uuid, 5, 4)), '-',
HEX(SUBSTR(uuid, 3, 2)), '-',
HEX(SUBSTR(uuid, 1, 2)), '-',
HEX(SUBSTR(uuid, 9, 2)), '-',
HEX(SUBSTR(uuid, 11))
)
FROM groups
WHERE deleted_at IS NOT NULL
);
SELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where provider = 'hubspot';
SELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133
SELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null
# [PASSWORD_DOTS]
select * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';
select
cp.*
# DISTINCT t.id
# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields
FROM crm_profiles cp
JOIN crm_configurations crm on crm.id = cp.crm_configuration_id
JOIN users u on u.id = cp.user_id
JOIN teams t ON t.id = crm.team_id
WHERE crm.provider = 'salesforce' and t.status = 'active'
and cp.archived_at IS NULL and u.deleted_at IS NULL
and t.id NOT IN (1093)
and t.id = 2
and cp.contact_fields IS NULL;
# and c.crm_provider_id = '003Uu00000ojD4NIAU';
SELECT * FROM users WHERE id = 26484;
SELECT * FROM crm_profiles WHERE user_id = 26484;
SELECT * FROM social_accounts WHERE sociable_id = 26484;
SELECT * FROM crm_configurations where provider = 'salesforce';
select * from users where id IN (10022, 10403);
select * from users where team_id IN (526);
select * from teams where id IN (526, 532);
select * from crm_configurations where id IN (500, 516);
select * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);
select * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';
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 = 526 and sa.provider = 'salesforce';
select * from team_settings where team_id IN (526, 532);
select * from users where id IN (22824);
select * from crm_profiles where crm_configuration_id IN (1026);
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 = 1093 and sa.provider = 'salesforce';
select * from teams where id = 1099;
select * from users where id = 29643
select * from activity_processing_states;
SELECT * FROM teams where name LIKE '%Fare%'; # 233
SELECT * FROM opportunities where crm_configuration_id = 215
# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'
;
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 = 1088 and sa.provider = 'hubspot';
SELECT * FROM teams order by updated_at DESC
SELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account
select * from crm_configurations where provider = 'pipedrive';
select * from teams where id = 957;
select * from crm_configurations where id = 957;
SELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743
SELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;
select * from users where team_id = 1; # 26726 - Gabriela Dureva
SELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific
select * from activities where user_id = 26726 order by id desc;
select * from contacts where crm_configuration_id = 1
and email IN ('[EMAIL]', '[EMAIL]'); # 2094416, 2093620
SELECT * FROM contacts WHERE id = 6284931;
SELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id
WHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;
select * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);
select * from crm_configurations where id = 1;
43801692-1aeb-32ce-acba-5b80a479701a
44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b
405975c0-b3d0-7aaa-821f-09d59cae6dd1
4caf848d-4bed-2299-b248-7788d41f9fca
49bedc3f-f196-eef3-89c3-dea6a3b4aa63
43420989-a09d-b8f8-9806-c8bbf7a02aac
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 = 1 and sa.provider = 'salesforce';
SELECT * FROM activities WHERE id = 75461988;
SELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;
select * from contacts where id = 17900517;
select * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id
where crm.provider != 'salesforce';
select * from users where id = 21047;
SELECT * FROM crm_configurations WHERE id = 892;
SELECT * FROM teams WHERE id = 942;
select * from opportunities where team_id = 942 order by updated_at desc;
select * from contacts where team_id = 942 order by updated_at desc;
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 = 942 and sa.provider = 'hubspot';
SELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430
SELECT * FROM crm_configurations WHERE id = 1;
SELECT * FROM teams WHERE crm_id = 1;
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 = 1 and sa.provider = 'salesforce';
select id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1
SELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430
select * from teams where id = 852;
select * from groups where id = 2286;
select * from sidekick_settings where team_id = 852;
select * from default_activity_types where team_id = 852;
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1 AND u.deleted_at IS NULL
AND u.crm_required = 1
AND u.team_id = 1
ORDER BY u.team_id;
SELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (
18481
);
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1
AND u.deleted_at IS NULL
AND u.crm_required = 1
# AND u.team_id = 1
AND p.id IS NULL -- Move this condition to WHERE clause
ORDER BY u.team_id;
SELECT * FROM opportunities WHERE id = 20002609;
select * from teams where id = 1122; # Velatir, 29953 - [EMAIL]
select * from crm_configurations where id = 1060;
select * from crm_layouts where crm_configuration_id = 1060;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;
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 = 1122 and sa.provider = 'hubspot';
select * from opportunities where team_id = 1122 order by updated_at desc;
select * from crm_field_data where object_type = 'contact';
SELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262
SELECT
CONCAT(u.id, CASE WHEN u.id = t.own...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85362
|
2923
|
9
|
2026-05-28T12:21:18.476242+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970878476_m2.jpg...
|
PhpStorm
|
faVsco.js – HubspotJournalPollingService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
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
45
1
41
66
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993
SELECT * FROM users WHERE id = 25061;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 994;
SELECT * FROM crm_profiles WHERE user_id = 25061;
select * from crm_configurations where id = 834;
SELECT * FROM teams WHERE id = 882;
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 = 882 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;
SELECT * FROM contacts where crm_configuration_id = 834;
SELECT * FROM opportunities WHERE team_id = 933
# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');
AND id IN (8482561,18352941,19042734,19232139,19445140,19472541);
SELECT * FROM opportunity_contacts
WHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 485; #
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
select crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id
where crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')
# and l.converted_at IS NOT NULL
;
# [PASSWORD_DOTS]
SELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')
and opportunity_id IS NULL
order by id desc;
SELECT * FROM teams WHERE id = 604; # 598
SELECT * FROM activities WHERE id = 74410828; # [EMAIL]
SELECT * FROM accounts WHERE id = 20068382;
SELECT * FROM accounts WHERE id = 35186038;
SELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;
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 = 559 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;
select * from sidekick_settings where team_id = 781;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100
SELECT * FROM crm_layouts WHERE crm_configuration_id = 711;
SELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL
and is_internal = 0 and status = 'completed'
order by id desc;
SELECT * FROM crm_layout_entities
WHERE crm_layout_id IN (2352, 2353);
;
SELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;
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 = 556 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;
SELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;
select * from contacts
where crm_configuration_id = 530
and crm_provider_id = 872252;
select * from activities where crm_configuration_id = 530
and user_id = 14343 and type like '%softphone%'
and created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);
SELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t
JOIN crm_configurations c ON t.id = c.team_id
WHERE t.status = 'active';
SELECT * FROM teams where id = 1091;
SELECT * FROM crm_configurations where team_id = 1091;
SELECT * FROM activity_providers where team_id = 1091;
SELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT * FROM teams WHERE name LIKE '%Leadventure%';
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 = 1091 and sa.provider = 'salesforce';
SELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812
SELECT * FROM teams where id = 862;
SELECT * FROM crm_configurations where team_id = 862;
SELECT * FROM activity_providers where team_id = 862;
SELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT t.id, crm.id, crm.provider, ap.* FROM teams t
join crm_configurations crm on t.id = crm.team_id
join activity_providers ap on t.id = ap.team_id
where t.status = 'active' and ap.is_enabled = 1
and crm.provider = 'hubspot'
and ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',
'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');
SELECT * FROM teams where id = 1068;
SELECT * FROM crm_configurations where team_id = 1068;
SELECT * FROM activity_providers where team_id = 1068;
SELECT * FROM activities a
where crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')
and a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'
)
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by a.id desc;
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 = 1068 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262
SELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;
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 = 882 and sa.provider = 'hubspot';
select * from crm_layouts where crm_configuration_id = 834;
select * from crm_layout_entities where crm_layout_id = 2780;
select * from crm_fields where id IN (321153,321192,321193,321194);
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
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 = 1057 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8
SELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20
SELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10
SELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #
SELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;
select * from users where team_id = 51; # 7783
SELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130
select * from activity_searches where user_id = 7783;
select * from activity_search_filters where activity_search_id IN (32291, 32292);
SELECT asf.activity_search_id, asf.id, asf.value
FROM activity_search_filters asf
WHERE asf.filter = 'group_id'
AND asf.value IN (
SELECT CONCAT(
HEX(SUBSTR(uuid, 5, 4)), '-',
HEX(SUBSTR(uuid, 3, 2)), '-',
HEX(SUBSTR(uuid, 1, 2)), '-',
HEX(SUBSTR(uuid, 9, 2)), '-',
HEX(SUBSTR(uuid, 11))
)
FROM groups
WHERE deleted_at IS NOT NULL
);
SELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where provider = 'hubspot';
SELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133
SELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null
# [PASSWORD_DOTS]
select * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';
select
cp.*
# DISTINCT t.id
# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields
FROM crm_profiles cp
JOIN crm_configurations crm on crm.id = cp.crm_configuration_id
JOIN users u on u.id = cp.user_id
JOIN teams t ON t.id = crm.team_id
WHERE crm.provider = 'salesforce' and t.status = 'active'
and cp.archived_at IS NULL and u.deleted_at IS NULL
and t.id NOT IN (1093)
and t.id = 2
and cp.contact_fields IS NULL;
# and c.crm_provider_id = '003Uu00000ojD4NIAU';
SELECT * FROM users WHERE id = 26484;
SELECT * FROM crm_profiles WHERE user_id = 26484;
SELECT * FROM social_accounts WHERE sociable_id = 26484;
SELECT * FROM crm_configurations where provider = 'salesforce';
select * from users where id IN (10022, 10403);
select * from users where team_id IN (526);
select * from teams where id IN (526, 532);
select * from crm_configurations where id IN (500, 516);
select * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);
select * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';
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 = 526 and sa.provider = 'salesforce';
select * from team_settings where team_id IN (526, 532);
select * from users where id IN (22824);
select * from crm_profiles where crm_configuration_id IN (1026);
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 = 1093 and sa.provider = 'salesforce';
select * from teams where id = 1099;
select * from users where id = 29643
select * from activity_processing_states;
SELECT * FROM teams where name LIKE '%Fare%'; # 233
SELECT * FROM opportunities where crm_configuration_id = 215
# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'
;
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 = 1088 and sa.provider = 'hubspot';
SELECT * FROM teams order by updated_at DESC
SELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account
select * from crm_configurations where provider = 'pipedrive';
select * from teams where id = 957;
select * from crm_configurations where id = 957;
SELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743
SELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;
select * from users where team_id = 1; # 26726 - Gabriela Dureva
SELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific
select * from activities where user_id = 26726 order by id desc;
select * from contacts where crm_configuration_id = 1
and email IN ('[EMAIL]', '[EMAIL]'); # 2094416, 2093620
SELECT * FROM contacts WHERE id = 6284931;
SELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id
WHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;
select * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);
select * from crm_configurations where id = 1;
43801692-1aeb-32ce-acba-5b80a479701a
44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b
405975c0-b3d0-7aaa-821f-09d59cae6dd1
4caf848d-4bed-2299-b248-7788d41f9fca
49bedc3f-f196-eef3-89c3-dea6a3b4aa63
43420989-a09d-b8f8-9806-c8bbf7a02aac
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 = 1 and sa.provider = 'salesforce';
SELECT * FROM activities WHERE id = 75461988;
SELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;
select * from contacts where id = 17900517;
select * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id
where crm.provider != 'salesforce';
select * from users where id = 21047;
SELECT * FROM crm_configurations WHERE id = 892;
SELECT * FROM teams WHERE id = 942;
select * from opportunities where team_id = 942 order by updated_at desc;
select * from contacts where team_id = 942 order by updated_at desc;
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 = 942 and sa.provider = 'hubspot';
SELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430
SELECT * FROM crm_configurations WHERE id = 1;
SELECT * FROM teams WHERE crm_id = 1;
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 = 1 and sa.provider = 'salesforce';
select id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1
SELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430
select * from teams where id = 852;
select * from groups where id = 2286;
select * from sidekick_settings where team_id = 852;
select * from default_activity_types where team_id = 852;
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1 AND u.deleted_at IS NULL
AND u.crm_required = 1
AND u.team_id = 1
ORDER BY u.team_id;
SELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (
18481
);
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1
AND u.deleted_at IS NULL
AND u.crm_required = 1
# AND u.team_id = 1
AND p.id IS NULL -- Move this condition to WHERE clause
ORDER BY u.team_id;
SELECT * FROM opportunities WHERE id = 20002609;
select * from teams where id = 1122; # Velatir, 29953 - [EMAIL]
select * from crm_configurations where id = 1060;
select * from crm_layouts where crm_configuration_id = 1060;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;
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 = 1122 and sa.provider = 'hubspot';
select * from opportunities where team_id = 1122 order by updated_at desc;
select * from crm_field_data where object_type = 'contact';
SELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262
SELECT
CONCAT(u.id, CASE WHEN u.id = t.own...
|
[{"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":"#12121 on JY-20963-fix-import-on-deleted-entity, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.11569149,"height":0.025538707},"on_screen":true,"help_text":"Pull request #12121 exists for current branch JY-20963-fix-import-on-deleted-entity","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.8374335,"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":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"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 'TextRelayServiceTest'","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 'TextRelayServiceTest'","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":"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":"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":"AXStaticText","text":"12","depth":4,"bounds":{"left":0.37865692,"top":0.15003991,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"9","depth":4,"bounds":{"left":0.39029256,"top":0.15003991,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39993352,"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.40724733,"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\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.41589096,"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.4245346,"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.43550533,"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.44414893,"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.45279256,"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.4637633,"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.47473404,"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.5013298,"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.51230055,"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.7237367,"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":"45","depth":4,"bounds":{"left":0.6938165,"top":0.123703115,"width":0.010305851,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.70611703,"top":0.123703115,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"41","depth":4,"bounds":{"left":0.71542555,"top":0.123703115,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"66","depth":4,"bounds":{"left":0.72706115,"top":0.123703115,"width":0.010305851,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.7390292,"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.7463431,"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":"SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993\nSELECT * FROM users WHERE id = 25061;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 994;\nSELECT * FROM crm_profiles WHERE user_id = 25061;\n\nselect * from crm_configurations where id = 834;\nSELECT * FROM teams WHERE id = 882;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;\n\nSELECT * FROM contacts where crm_configuration_id = 834;\nSELECT * FROM opportunities WHERE team_id = 933\n# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');\nAND id IN (8482561,18352941,19042734,19232139,19445140,19472541);\nSELECT * FROM opportunity_contacts\nWHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 485; #\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nselect crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id\nwhere crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')\n# and l.converted_at IS NOT NULL\n;\n\n# ********************************************************************\nSELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')\nand opportunity_id IS NULL\norder by id desc;\n\nSELECT * FROM teams WHERE id = 604; # 598\nSELECT * FROM activities WHERE id = 74410828; # chelseaw@allvoices.co\nSELECT * FROM accounts WHERE id = 20068382;\nSELECT * FROM accounts WHERE id = 35186038;\n\nSELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 559 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;\nselect * from sidekick_settings where team_id = 781;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100\n\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 711;\nSELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL\nand is_internal = 0 and status = 'completed'\norder by id desc;\n\nSELECT * FROM crm_layout_entities\nWHERE crm_layout_id IN (2352, 2353);\n;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 556 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;\nSELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;\nselect * from contacts\nwhere crm_configuration_id = 530\nand crm_provider_id = 872252;\n\nselect * from activities where crm_configuration_id = 530\nand user_id = 14343 and type like '%softphone%'\nand created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);\n\n\nSELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t\nJOIN crm_configurations c ON t.id = c.team_id\nWHERE t.status = 'active';\n\nSELECT * FROM teams where id = 1091;\nSELECT * FROM crm_configurations where team_id = 1091;\nSELECT * FROM activity_providers where team_id = 1091;\nSELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT * FROM teams WHERE name LIKE '%Leadventure%';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1091 and sa.provider = 'salesforce';\n\nSELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812\nSELECT * FROM teams where id = 862;\nSELECT * FROM crm_configurations where team_id = 862;\nSELECT * FROM activity_providers where team_id = 862;\nSELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT t.id, crm.id, crm.provider, ap.* FROM teams t\njoin crm_configurations crm on t.id = crm.team_id\njoin activity_providers ap on t.id = ap.team_id\nwhere t.status = 'active' and ap.is_enabled = 1\nand crm.provider = 'hubspot'\nand ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',\n 'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');\n\nSELECT * FROM teams where id = 1068;\nSELECT * FROM crm_configurations where team_id = 1068;\nSELECT * FROM activity_providers where team_id = 1068;\n\nSELECT * FROM activities a\nwhere crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')\nand a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'\n )\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by a.id desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1068 and sa.provider = 'hubspot';\n\n# ********************************************************************\n# ********************************************************************\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262\nSELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\nselect * from crm_layouts where crm_configuration_id = 834;\nselect * from crm_layout_entities where crm_layout_id = 2780;\nselect * from crm_fields where id IN (321153,321192,321193,321194);\n\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1057 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8\n\nSELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20\n\nSELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10\n\nSELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #\n\nSELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;\nselect * from users where team_id = 51; # 7783\nSELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130\nselect * from activity_searches where user_id = 7783;\nselect * from activity_search_filters where activity_search_id IN (32291, 32292);\n\nSELECT asf.activity_search_id, asf.id, asf.value\nFROM activity_search_filters asf\nWHERE asf.filter = 'group_id'\nAND asf.value IN (\n SELECT CONCAT(\n HEX(SUBSTR(uuid, 5, 4)), '-',\n HEX(SUBSTR(uuid, 3, 2)), '-',\n HEX(SUBSTR(uuid, 1, 2)), '-',\n HEX(SUBSTR(uuid, 9, 2)), '-',\n HEX(SUBSTR(uuid, 11))\n )\n FROM groups\n WHERE deleted_at IS NOT NULL\n);\n\nSELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th\n# ********************************************************************\nSELECT * FROM crm_configurations where provider = 'hubspot';\nSELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133\nSELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null\n# ********************************************************************\n\nselect * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';\nselect\n cp.*\n# DISTINCT t.id\n# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields\nFROM crm_profiles cp\nJOIN crm_configurations crm on crm.id = cp.crm_configuration_id\nJOIN users u on u.id = cp.user_id\nJOIN teams t ON t.id = crm.team_id\nWHERE crm.provider = 'salesforce' and t.status = 'active'\n and cp.archived_at IS NULL and u.deleted_at IS NULL\n and t.id NOT IN (1093)\n and t.id = 2\n and cp.contact_fields IS NULL;\n# and c.crm_provider_id = '003Uu00000ojD4NIAU';\n\nSELECT * FROM users WHERE id = 26484;\nSELECT * FROM crm_profiles WHERE user_id = 26484;\nSELECT * FROM social_accounts WHERE sociable_id = 26484;\nSELECT * FROM crm_configurations where provider = 'salesforce';\nselect * from users where id IN (10022, 10403);\nselect * from users where team_id IN (526);\nselect * from teams where id IN (526, 532);\nselect * from crm_configurations where id IN (500, 516);\nselect * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);\nselect * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 526 and sa.provider = 'salesforce';\nselect * from team_settings where team_id IN (526, 532);\n\nselect * from users where id IN (22824);\nselect * from crm_profiles where crm_configuration_id IN (1026);\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1093 and sa.provider = 'salesforce';\n\nselect * from teams where id = 1099;\nselect * from users where id = 29643\n\nselect * from activity_processing_states;\n\nSELECT * FROM teams where name LIKE '%Fare%'; # 233\nSELECT * FROM opportunities where crm_configuration_id = 215\n# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1088 and sa.provider = 'hubspot';\n\nSELECT * FROM teams order by updated_at DESC\nSELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account\n\nselect * from crm_configurations where provider = 'pipedrive';\n\nselect * from teams where id = 957;\nselect * from crm_configurations where id = 957;\n\nSELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743\nSELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;\n\nselect * from users where team_id = 1; # 26726 - Gabriela Dureva\nSELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific\nselect * from activities where user_id = 26726 order by id desc;\nselect * from contacts where crm_configuration_id = 1\nand email IN ('charlotte.ward@prolific.com', 'frankie.bryant@prolific.com'); # 2094416, 2093620\nSELECT * FROM contacts WHERE id = 6284931;\n\nSELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id\nWHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;\n\nselect * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);\nselect * from crm_configurations where id = 1;\n\n43801692-1aeb-32ce-acba-5b80a479701a\n44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b\n405975c0-b3d0-7aaa-821f-09d59cae6dd1\n4caf848d-4bed-2299-b248-7788d41f9fca\n49bedc3f-f196-eef3-89c3-dea6a3b4aa63\n43420989-a09d-b8f8-9806-c8bbf7a02aac\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nSELECT * FROM activities WHERE id = 75461988;\n\nSELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;\n\nselect * from contacts where id = 17900517;\n\nselect * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id\nwhere crm.provider != 'salesforce';\n\nselect * from users where id = 21047;\nSELECT * FROM crm_configurations WHERE id = 892;\nSELECT * FROM teams WHERE id = 942;\nselect * from opportunities where team_id = 942 order by updated_at desc;\nselect * from contacts where team_id = 942 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 942 and sa.provider = 'hubspot';\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430\nSELECT * FROM crm_configurations WHERE id = 1;\nSELECT * FROM teams WHERE crm_id = 1;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nselect id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1\nSELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430\n\nselect * from teams where id = 852;\nselect * from groups where id = 2286;\nselect * from sidekick_settings where team_id = 852;\nselect * from default_activity_types where team_id = 852;\n\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1 AND u.deleted_at IS NULL\nAND u.crm_required = 1\nAND u.team_id = 1\nORDER BY u.team_id;\n\nSELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (\n18481\n );\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1\n AND u.deleted_at IS NULL\n AND u.crm_required = 1\n# AND u.team_id = 1\n AND p.id IS NULL -- Move this condition to WHERE clause\nORDER BY u.team_id;\n\nSELECT * FROM opportunities WHERE id = 20002609;\nselect * from teams where id = 1122; # Velatir, 29953 - christian@velatir.com\nselect * from crm_configurations where id = 1060;\nselect * from crm_layouts where crm_configuration_id = 1060;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1122 and sa.provider = 'hubspot';\nselect * from opportunities where team_id = 1122 order by updated_at desc;\n\nselect * from crm_field_data where object_type = 'contact';\n\nSELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 248 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS\nSELECT * FROM users where id = 24115;\nSELECT * FROM accounts where id = 4002896;\nSELECT * FROM teams WHERE name LIKE '%adswerve%';\nSELECT * FROM opportunities where crm_configuration_id = 230 AND crm_provider_id IN (\"0069N000003GIQ9QAO\",\"0061r000019yGP9AAM\",\"0066900001S2KWlAAN\",\"0066900001TDpj2AAD\",\"0066900001b8uEwAAI\",\"0069N000001rQi0QAE\",\"006QF00000KD40mYAD\",\"006QF00000LzpRJYAZ\",\"0069N000002uomtQAA\",\"0069N000002xlMLQAY\",\"0066900001NV6ubAAD\",\"0061r00001HJp45AAD\",\"006QF00000uTlUoYAK\",\"006QF00000v0bZqYAI\");\nSELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203\n\nSELECT u.id, u.email, ac.name, a.* FROM activities a\nJOIN users u ON a.user_id = u.id\nJOIN accounts ac ON a.account_id = ac.id\nWHERE\nuuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or\nuuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or\nuuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;\n\nselect * from users where id = 5825;\nSELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;\n\nselect * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;\n19594, 862\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 862 and sa.provider = 'salesforce';\n\nselect * from automated_reports where id = 36;\nselect ar.frequency, r.*, ar.* from automated_report_results r\njoin automated_reports ar on r.report_id = ar.id\nwhere ar.frequency != 'one_off';\n\nselect s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;\nselect * from nudges n where n.activity_search_id\n\nselect * from teams where created_at > '2026-03-09';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;\n\nselect * from users where team_id = 1 and name like '%Lukas%'; # 7160\n\nSELECT * FROM teams WHERE id = 575;\nselect * from opportunities where team_id = 575;\nSELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,\nselect * from opportunities where team_id = 1126;\nSELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,\nselect * from opportunities where team_id = 1125;\nselect * from contacts c\nwhere c.team_id = 882;\n\nSELECT * FROM activities WHERE id = 76822967;\nSELECT * FROM crm_profiles WHERE user_id = 15440;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 555;\nSELECT * FROM crm_configurations WHERE id = 555;\nSELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 581 and sa.provider = 'salesforce';\n\nSELECT * FROM automated_report_results order by id desc;\n\nselect * from features;\nselect * from team_features where feature_id = 40;\n\nselect * from teams where id = 556;\n\nselect * from automated_reports;\nwhere id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , [\"pdf\",\"podcast\"]\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\nselect * from automated_report_results order by id desc;\nSELECT * FROM automated_report_results WHERE id = 1919;\n\nselect * from automated_report_results WHERE report_id = 54;\n\nselect * from opportunities where id = 7594349;\n\nSELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067 - jiminnyintegration@lesmills.com\nselect * from playbooks where team_id = 711; # event 226147\nSELECT * FROM playbook_categories WHERE playbook_id = 5515;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event';\nSELECT * FROM crm_fields WHERE id = 226147;\nSELECT * FROM crm_field_values WHERE crm_field_id = 226147;\n\nSELECT * FROM crm_configurations WHERE id = 692;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 711 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles cp JOIN users u on u.id = cp.user_id WHERE u.team_id = 711;\n\nselect * from leads;\n\nselect * from calendars;\n\nSELECT\n t.id AS team_id,\n t.name,\n LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1)) AS calendar_domain\nFROM teams t\nJOIN users u ON u.team_id = t.id\nJOIN calendars c ON c.user_id = u.id AND c.status = 'active' AND c.calendar_provider_id LIKE '%@%'\nLEFT JOIN team_domains td\n ON td.team_id = t.id\n AND td.deleted_at IS NULL\n AND td.domain = LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1))\nGROUP BY t.id, t.name, calendar_domain\nORDER BY t.name, calendar_domain;\n\nselect * from users u join calendars c on c.user_id = u.id\nwhere u.team_id = 882;\n\n\nselect * from activities where id = 74049485; # team 563 crm 537\nselect * from activities where id = 73272382; # team 563 crm 537\nselect * from activities where id = 64400389; # team 563 crm 537\nselect * from activities where id = 58081273; # team 563 crm 537\nselect * from activities where id = 54520297; # team 563 crm 537\nselect * from participants where activity_id = 58081273;\n\nselect * from activities where crm_configuration_id = 537 and provider = 'aircall'\nand account_id = 19003658 order by updated_at desc;\n\nselect * from contacts where crm_configuration_id = 537 and id = 35957759;\nselect * from accounts where crm_configuration_id = 537 and id = 19003658;\n\nselect * from automated_report_results where id = 1976;\nselect * from automated_reports where id = 583;\nselect * from activity_searches where id = 87714;\nselect * from activity_search_filters where activity_search_id = 87714;\n\nSELECT * FROM activities WHERE uuid_to_bin('8827f672-202d-4162-9d04-73ff5f0566a9') = uuid\nor uuid_to_bin('47842446-af51-4bcb-854f-cc6560290101') = uuid;\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot';\nselect * from rate_limits;\n\nselect * from automated_report_results where media_type = 'pdf' and status = 2\nand id IN (18, 1872);\nselect * from automated_reports where id = 54;\nSELECT * FROM users WHERE id IN (24623,29443,29613);\n\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\n\nselect * from text_relays where created_at > '2026-05-01';\nand id IN (32415, 32416);\n# and id = 32412;\n\nselect * from users where team_id = 2 and email like '%scott%' and id = 29510;\n\nSELECT * FROM activities WHERE uuid_to_bin('67cebfc2-ed56-44a2-8c68-7a0286ed8618') = uuid; # 79763436\n\nSELECT email_provider_id, COUNT(*) as count, GROUP_CONCAT(id) as ids, GROUP_CONCAT(status) as statuses\nFROM text_relays\nWHERE email_provider_id IN ('19e2027868a64b42', '19e2033ed8ea6b10')\nGROUP BY email_provider_id;\nSELECT id, status, telephony_provider_id, created_at\nFROM activities\nWHERE id IN (80028719, 80028846);\nSELECT id, status, code, email_sent_at, created_at, updated_at\nFROM text_relays\nWHERE id IN (32415, 32416);\nSELECT id, status, code, sender, recipient, created_at\nFROM text_relays\nWHERE sender LIKE '%mario.georgiev%' OR sender LIKE '%stoyan.tomov%'\nORDER BY created_at DESC\nLIMIT 10;\n\nSELECT id, uuid, status, code, sender, recipient, created_at, updated_at\nFROM text_relays\nWHERE uuid = uuid_to_bin('0626141c-27a6-4d8c-aff8-7c8020a2c656');\n\n# ***************\nSELECT DISTINCT u.id, u.email, u.name, u.softphone_number, COUNT(a.id) as sms_count\nFROM users u\nINNER JOIN activities a ON u.id = .user_id\nWHERE a.type LIKE 'sms%'\nAND a.created_at > DATE_SUB(NOW(), INTERVAL 30 DAY)\nGROUP BY u.id, u.email, u.name, u.softphone_number\nORDER BY sms_count DESC;\n\nselect * from teams where id = 1;\n\nselect * from roles;\n\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1117 and sa.provider = 'hubspot';\nSELECT * FROM activities WHERE uuid_to_bin('8024fffb-2df7-4017-91f4-d9f896050248') = uuid; # 79933459 YES\nSELECT * FROM activities WHERE uuid_to_bin('25529043-8094-4781-927f-4f4da2a8185c') = uuid; # 80186192 NO\nSELECT * FROM crm_configurations WHERE id = 1053;\nSELECT * FROM teams WHERE id = 1117;\nselect * from users where id = 30249;\nselect * from playbooks where id = 5473;\nselect * from playbook_categories where id = 43783;\nselect * from playbook_categories where playbook_id = 5473;\nselect * from crm_fields where id = 659242;\nselect * from crm_field_values where crm_field_id = 659242;\n\nSELECT * FROM crm_field_data fd\n# JOIN crm_fields f ON fd.crm_field_id = f.id\n# JOIN activities a ON fd.activity_id = a.id\nWHERE activity_id = 79933459\n# AND f.crm_provider_id = 'hs_activity_type';\n\n\nSELECT * FROM activity_messages;\nselect * from text_relays where created_at > '2026-05-01';\nselect * from activities where user_id IN (7160, 18608) and created_at > '2026-05-22' order by id desc;\nselect * from users where team_id = 1 and id IN (18608, 13934, 7160);\nselect * from activities where user_id = 7160 order by id desc limit 10;\n\nselect * from accounts where team_id = 1 and name = 'Column5';\n\nselect * from users where name like '%Subra%'; # 31054, 1117\nselect * from teams where id = 1117;\nselect * from activity_searches where user_id = 31054;\nselect * from activity_search_filters where activity_search_id IN (88882, 88902);","depth":4,"on_screen":true,"value":"SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993\nSELECT * FROM users WHERE id = 25061;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 994;\nSELECT * FROM crm_profiles WHERE user_id = 25061;\n\nselect * from crm_configurations where id = 834;\nSELECT * FROM teams WHERE id = 882;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;\n\nSELECT * FROM contacts where crm_configuration_id = 834;\nSELECT * FROM opportunities WHERE team_id = 933\n# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');\nAND id IN (8482561,18352941,19042734,19232139,19445140,19472541);\nSELECT * FROM opportunity_contacts\nWHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 485; #\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nselect crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id\nwhere crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')\n# and l.converted_at IS NOT NULL\n;\n\n# ********************************************************************\nSELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')\nand opportunity_id IS NULL\norder by id desc;\n\nSELECT * FROM teams WHERE id = 604; # 598\nSELECT * FROM activities WHERE id = 74410828; # chelseaw@allvoices.co\nSELECT * FROM accounts WHERE id = 20068382;\nSELECT * FROM accounts WHERE id = 35186038;\n\nSELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 559 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;\nselect * from sidekick_settings where team_id = 781;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100\n\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 711;\nSELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL\nand is_internal = 0 and status = 'completed'\norder by id desc;\n\nSELECT * FROM crm_layout_entities\nWHERE crm_layout_id IN (2352, 2353);\n;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 556 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;\nSELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;\nselect * from contacts\nwhere crm_configuration_id = 530\nand crm_provider_id = 872252;\n\nselect * from activities where crm_configuration_id = 530\nand user_id = 14343 and type like '%softphone%'\nand created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);\n\n\nSELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t\nJOIN crm_configurations c ON t.id = c.team_id\nWHERE t.status = 'active';\n\nSELECT * FROM teams where id = 1091;\nSELECT * FROM crm_configurations where team_id = 1091;\nSELECT * FROM activity_providers where team_id = 1091;\nSELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT * FROM teams WHERE name LIKE '%Leadventure%';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1091 and sa.provider = 'salesforce';\n\nSELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812\nSELECT * FROM teams where id = 862;\nSELECT * FROM crm_configurations where team_id = 862;\nSELECT * FROM activity_providers where team_id = 862;\nSELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT t.id, crm.id, crm.provider, ap.* FROM teams t\njoin crm_configurations crm on t.id = crm.team_id\njoin activity_providers ap on t.id = ap.team_id\nwhere t.status = 'active' and ap.is_enabled = 1\nand crm.provider = 'hubspot'\nand ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',\n 'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');\n\nSELECT * FROM teams where id = 1068;\nSELECT * FROM crm_configurations where team_id = 1068;\nSELECT * FROM activity_providers where team_id = 1068;\n\nSELECT * FROM activities a\nwhere crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')\nand a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'\n )\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by a.id desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1068 and sa.provider = 'hubspot';\n\n# ********************************************************************\n# ********************************************************************\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262\nSELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\nselect * from crm_layouts where crm_configuration_id = 834;\nselect * from crm_layout_entities where crm_layout_id = 2780;\nselect * from crm_fields where id IN (321153,321192,321193,321194);\n\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1057 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8\n\nSELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20\n\nSELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10\n\nSELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #\n\nSELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;\nselect * from users where team_id = 51; # 7783\nSELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130\nselect * from activity_searches where user_id = 7783;\nselect * from activity_search_filters where activity_search_id IN (32291, 32292);\n\nSELECT asf.activity_search_id, asf.id, asf.value\nFROM activity_search_filters asf\nWHERE asf.filter = 'group_id'\nAND asf.value IN (\n SELECT CONCAT(\n HEX(SUBSTR(uuid, 5, 4)), '-',\n HEX(SUBSTR(uuid, 3, 2)), '-',\n HEX(SUBSTR(uuid, 1, 2)), '-',\n HEX(SUBSTR(uuid, 9, 2)), '-',\n HEX(SUBSTR(uuid, 11))\n )\n FROM groups\n WHERE deleted_at IS NOT NULL\n);\n\nSELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th\n# ********************************************************************\nSELECT * FROM crm_configurations where provider = 'hubspot';\nSELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133\nSELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null\n# ********************************************************************\n\nselect * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';\nselect\n cp.*\n# DISTINCT t.id\n# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields\nFROM crm_profiles cp\nJOIN crm_configurations crm on crm.id = cp.crm_configuration_id\nJOIN users u on u.id = cp.user_id\nJOIN teams t ON t.id = crm.team_id\nWHERE crm.provider = 'salesforce' and t.status = 'active'\n and cp.archived_at IS NULL and u.deleted_at IS NULL\n and t.id NOT IN (1093)\n and t.id = 2\n and cp.contact_fields IS NULL;\n# and c.crm_provider_id = '003Uu00000ojD4NIAU';\n\nSELECT * FROM users WHERE id = 26484;\nSELECT * FROM crm_profiles WHERE user_id = 26484;\nSELECT * FROM social_accounts WHERE sociable_id = 26484;\nSELECT * FROM crm_configurations where provider = 'salesforce';\nselect * from users where id IN (10022, 10403);\nselect * from users where team_id IN (526);\nselect * from teams where id IN (526, 532);\nselect * from crm_configurations where id IN (500, 516);\nselect * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);\nselect * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 526 and sa.provider = 'salesforce';\nselect * from team_settings where team_id IN (526, 532);\n\nselect * from users where id IN (22824);\nselect * from crm_profiles where crm_configuration_id IN (1026);\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1093 and sa.provider = 'salesforce';\n\nselect * from teams where id = 1099;\nselect * from users where id = 29643\n\nselect * from activity_processing_states;\n\nSELECT * FROM teams where name LIKE '%Fare%'; # 233\nSELECT * FROM opportunities where crm_configuration_id = 215\n# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1088 and sa.provider = 'hubspot';\n\nSELECT * FROM teams order by updated_at DESC\nSELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account\n\nselect * from crm_configurations where provider = 'pipedrive';\n\nselect * from teams where id = 957;\nselect * from crm_configurations where id = 957;\n\nSELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743\nSELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;\n\nselect * from users where team_id = 1; # 26726 - Gabriela Dureva\nSELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific\nselect * from activities where user_id = 26726 order by id desc;\nselect * from contacts where crm_configuration_id = 1\nand email IN ('charlotte.ward@prolific.com', 'frankie.bryant@prolific.com'); # 2094416, 2093620\nSELECT * FROM contacts WHERE id = 6284931;\n\nSELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id\nWHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;\n\nselect * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);\nselect * from crm_configurations where id = 1;\n\n43801692-1aeb-32ce-acba-5b80a479701a\n44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b\n405975c0-b3d0-7aaa-821f-09d59cae6dd1\n4caf848d-4bed-2299-b248-7788d41f9fca\n49bedc3f-f196-eef3-89c3-dea6a3b4aa63\n43420989-a09d-b8f8-9806-c8bbf7a02aac\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nSELECT * FROM activities WHERE id = 75461988;\n\nSELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;\n\nselect * from contacts where id = 17900517;\n\nselect * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id\nwhere crm.provider != 'salesforce';\n\nselect * from users where id = 21047;\nSELECT * FROM crm_configurations WHERE id = 892;\nSELECT * FROM teams WHERE id = 942;\nselect * from opportunities where team_id = 942 order by updated_at desc;\nselect * from contacts where team_id = 942 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 942 and sa.provider = 'hubspot';\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430\nSELECT * FROM crm_configurations WHERE id = 1;\nSELECT * FROM teams WHERE crm_id = 1;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nselect id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1\nSELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430\n\nselect * from teams where id = 852;\nselect * from groups where id = 2286;\nselect * from sidekick_settings where team_id = 852;\nselect * from default_activity_types where team_id = 852;\n\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1 AND u.deleted_at IS NULL\nAND u.crm_required = 1\nAND u.team_id = 1\nORDER BY u.team_id;\n\nSELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (\n18481\n );\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1\n AND u.deleted_at IS NULL\n AND u.crm_required = 1\n# AND u.team_id = 1\n AND p.id IS NULL -- Move this condition to WHERE clause\nORDER BY u.team_id;\n\nSELECT * FROM opportunities WHERE id = 20002609;\nselect * from teams where id = 1122; # Velatir, 29953 - christian@velatir.com\nselect * from crm_configurations where id = 1060;\nselect * from crm_layouts where crm_configuration_id = 1060;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1122 and sa.provider = 'hubspot';\nselect * from opportunities where team_id = 1122 order by updated_at desc;\n\nselect * from crm_field_data where object_type = 'contact';\n\nSELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 248 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS\nSELECT * FROM users where id = 24115;\nSELECT * FROM accounts where id = 4002896;\nSELECT * FROM teams WHERE name LIKE '%adswerve%';\nSELECT * FROM opportunities where crm_configuration_id = 230 AND crm_provider_id IN (\"0069N000003GIQ9QAO\",\"0061r000019yGP9AAM\",\"0066900001S2KWlAAN\",\"0066900001TDpj2AAD\",\"0066900001b8uEwAAI\",\"0069N000001rQi0QAE\",\"006QF00000KD40mYAD\",\"006QF00000LzpRJYAZ\",\"0069N000002uomtQAA\",\"0069N000002xlMLQAY\",\"0066900001NV6ubAAD\",\"0061r00001HJp45AAD\",\"006QF00000uTlUoYAK\",\"006QF00000v0bZqYAI\");\nSELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203\n\nSELECT u.id, u.email, ac.name, a.* FROM activities a\nJOIN users u ON a.user_id = u.id\nJOIN accounts ac ON a.account_id = ac.id\nWHERE\nuuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or\nuuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or\nuuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;\n\nselect * from users where id = 5825;\nSELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;\n\nselect * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;\n19594, 862\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 862 and sa.provider = 'salesforce';\n\nselect * from automated_reports where id = 36;\nselect ar.frequency, r.*, ar.* from automated_report_results r\njoin automated_reports ar on r.report_id = ar.id\nwhere ar.frequency != 'one_off';\n\nselect s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;\nselect * from nudges n where n.activity_search_id\n\nselect * from teams where created_at > '2026-03-09';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;\n\nselect * from users where team_id = 1 and name like '%Lukas%'; # 7160\n\nSELECT * FROM teams WHERE id = 575;\nselect * from opportunities where team_id = 575;\nSELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,\nselect * from opportunities where team_id = 1126;\nSELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,\nselect * from opportunities where team_id = 1125;\nselect * from contacts c\nwhere c.team_id = 882;\n\nSELECT * FROM activities WHERE id = 76822967;\nSELECT * FROM crm_profiles WHERE user_id = 15440;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 555;\nSELECT * FROM crm_configurations WHERE id = 555;\nSELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 581 and sa.provider = 'salesforce';\n\nSELECT * FROM automated_report_results order by id desc;\n\nselect * from features;\nselect * from team_features where feature_id = 40;\n\nselect * from teams where id = 556;\n\nselect * from automated_reports;\nwhere id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , [\"pdf\",\"podcast\"]\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\nselect * from automated_report_results order by id desc;\nSELECT * FROM automated_report_results WHERE id = 1919;\n\nselect * from automated_report_results WHERE report_id = 54;\n\nselect * from opportunities where id = 7594349;\n\nSELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067 - jiminnyintegration@lesmills.com\nselect * from playbooks where team_id = 711; # event 226147\nSELECT * FROM playbook_categories WHERE playbook_id = 5515;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event';\nSELECT * FROM crm_fields WHERE id = 226147;\nSELECT * FROM crm_field_values WHERE crm_field_id = 226147;\n\nSELECT * FROM crm_configurations WHERE id = 692;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 711 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles cp JOIN users u on u.id = cp.user_id WHERE u.team_id = 711;\n\nselect * from leads;\n\nselect * from calendars;\n\nSELECT\n t.id AS team_id,\n t.name,\n LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1)) AS calendar_domain\nFROM teams t\nJOIN users u ON u.team_id = t.id\nJOIN calendars c ON c.user_id = u.id AND c.status = 'active' AND c.calendar_provider_id LIKE '%@%'\nLEFT JOIN team_domains td\n ON td.team_id = t.id\n AND td.deleted_at IS NULL\n AND td.domain = LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1))\nGROUP BY t.id, t.name, calendar_domain\nORDER BY t.name, calendar_domain;\n\nselect * from users u join calendars c on c.user_id = u.id\nwhere u.team_id = 882;\n\n\nselect * from activities where id = 74049485; # team 563 crm 537\nselect * from activities where id = 73272382; # team 563 crm 537\nselect * from activities where id = 64400389; # team 563 crm 537\nselect * from activities where id = 58081273; # team 563 crm 537\nselect * from activities where id = 54520297; # team 563 crm 537\nselect * from participants where activity_id = 58081273;\n\nselect * from activities where crm_configuration_id = 537 and provider = 'aircall'\nand account_id = 19003658 order by updated_at desc;\n\nselect * from contacts where crm_configuration_id = 537 and id = 35957759;\nselect * from accounts where crm_configuration_id = 537 and id = 19003658;\n\nselect * from automated_report_results where id = 1976;\nselect * from automated_reports where id = 583;\nselect * from activity_searches where id = 87714;\nselect * from activity_search_filters where activity_search_id = 87714;\n\nSELECT * FROM activities WHERE uuid_to_bin('8827f672-202d-4162-9d04-73ff5f0566a9') = uuid\nor uuid_to_bin('47842446-af51-4bcb-854f-cc6560290101') = uuid;\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot';\nselect * from rate_limits;\n\nselect * from automated_report_results where media_type = 'pdf' and status = 2\nand id IN (18, 1872);\nselect * from automated_reports where id = 54;\nSELECT * FROM users WHERE id IN (24623,29443,29613);\n\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\n\nselect * from text_relays where created_at > '2026-05-01';\nand id IN (32415, 32416);\n# and id = 32412;\n\nselect * from users where team_id = 2 and email like '%scott%' and id = 29510;\n\nSELECT * FROM activities WHERE uuid_to_bin('67cebfc2-ed56-44a2-8c68-7a0286ed8618') = uuid; # 79763436\n\nSELECT email_provider_id, COUNT(*) as count, GROUP_CONCAT(id) as ids, GROUP_CONCAT(status) as statuses\nFROM text_relays\nWHERE email_provider_id IN ('19e2027868a64b42', '19e2033ed8ea6b10')\nGROUP BY email_provider_id;\nSELECT id, status, telephony_provider_id, created_at\nFROM activities\nWHERE id IN (80028719, 80028846);\nSELECT id, status, code, email_sent_at, created_at, updated_at\nFROM text_relays\nWHERE id IN (32415, 32416);\nSELECT id, status, code, sender, recipient, created_at\nFROM text_relays\nWHERE sender LIKE '%mario.georgiev%' OR sender LIKE '%stoyan.tomov%'\nORDER BY created_at DESC\nLIMIT 10;\n\nSELECT id, uuid, status, code, sender, recipient, created_at, updated_at\nFROM text_relays\nWHERE uuid = uuid_to_bin('0626141c-27a6-4d8c-aff8-7c8020a2c656');\n\n# ***************\nSELECT DISTINCT u.id, u.email, u.name, u.softphone_number, COUNT(a.id) as sms_count\nFROM users u\nINNER JOIN activities a ON u.id = .user_id\nWHERE a.type LIKE 'sms%'\nAND a.created_at > DATE_SUB(NOW(), INTERVAL 30 DAY)\nGROUP BY u.id, u.email, u.name, u.softphone_number\nORDER BY sms_count DESC;\n\nselect * from teams where id = 1;\n\nselect * from roles;\n\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1117 and sa.provider = 'hubspot';\nSELECT * FROM activities WHERE uuid_to_bin('8024fffb-2df7-4017-91f4-d9f896050248') = uuid; # 79933459 YES\nSELECT * FROM activities WHERE uuid_to_bin('25529043-8094-4781-927f-4f4da2a8185c') = uuid; # 80186192 NO\nSELECT * FROM crm_configurations WHERE id = 1053;\nSELECT * FROM teams WHERE id = 1117;\nselect * from users where id = 30249;\nselect * from playbooks where id = 5473;\nselect * from playbook_categories where id = 43783;\nselect * from playbook_categories where playbook_id = 5473;\nselect * from crm_fields where id = 659242;\nselect * from crm_field_values where crm_field_id = 659242;\n\nSELECT * FROM crm_field_data fd\n# JOIN crm_fields f ON fd.crm_field_id = f.id\n# JOIN activities a ON fd.activity_id = a.id\nWHERE activity_id = 79933459\n# AND f.crm_provider_id = 'hs_activity_type';\n\n\nSELECT * FROM activity_messages;\nselect * from text_relays where created_at > '2026-05-01';\nselect * from activities where user_id IN (7160, 18608) and created_at > '2026-05-22' order by id desc;\nselect * from users where team_id = 1 and id IN (18608, 13934, 7160);\nselect * from activities where user_id = 7160 order by id desc limit 10;\n\nselect * from accounts where team_id = 1 and name = 'Column5';\n\nselect * from users where name like '%Subra%'; # 31054, 1117\nselect * from teams where id = 1117;\nselect * from activity_searches where user_id = 31054;\nselect * from activity_search_filters where activity_search_id IN (88882, 88902);","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}]...
|
2526630547225287665
|
1137771414947247693
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
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
45
1
41
66
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993
SELECT * FROM users WHERE id = 25061;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 994;
SELECT * FROM crm_profiles WHERE user_id = 25061;
select * from crm_configurations where id = 834;
SELECT * FROM teams WHERE id = 882;
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 = 882 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;
SELECT * FROM contacts where crm_configuration_id = 834;
SELECT * FROM opportunities WHERE team_id = 933
# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');
AND id IN (8482561,18352941,19042734,19232139,19445140,19472541);
SELECT * FROM opportunity_contacts
WHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 485; #
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
select crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id
where crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')
# and l.converted_at IS NOT NULL
;
# [PASSWORD_DOTS]
SELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')
and opportunity_id IS NULL
order by id desc;
SELECT * FROM teams WHERE id = 604; # 598
SELECT * FROM activities WHERE id = 74410828; # [EMAIL]
SELECT * FROM accounts WHERE id = 20068382;
SELECT * FROM accounts WHERE id = 35186038;
SELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;
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 = 559 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;
select * from sidekick_settings where team_id = 781;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100
SELECT * FROM crm_layouts WHERE crm_configuration_id = 711;
SELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL
and is_internal = 0 and status = 'completed'
order by id desc;
SELECT * FROM crm_layout_entities
WHERE crm_layout_id IN (2352, 2353);
;
SELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;
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 = 556 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;
SELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;
select * from contacts
where crm_configuration_id = 530
and crm_provider_id = 872252;
select * from activities where crm_configuration_id = 530
and user_id = 14343 and type like '%softphone%'
and created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);
SELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t
JOIN crm_configurations c ON t.id = c.team_id
WHERE t.status = 'active';
SELECT * FROM teams where id = 1091;
SELECT * FROM crm_configurations where team_id = 1091;
SELECT * FROM activity_providers where team_id = 1091;
SELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT * FROM teams WHERE name LIKE '%Leadventure%';
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 = 1091 and sa.provider = 'salesforce';
SELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812
SELECT * FROM teams where id = 862;
SELECT * FROM crm_configurations where team_id = 862;
SELECT * FROM activity_providers where team_id = 862;
SELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT t.id, crm.id, crm.provider, ap.* FROM teams t
join crm_configurations crm on t.id = crm.team_id
join activity_providers ap on t.id = ap.team_id
where t.status = 'active' and ap.is_enabled = 1
and crm.provider = 'hubspot'
and ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',
'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');
SELECT * FROM teams where id = 1068;
SELECT * FROM crm_configurations where team_id = 1068;
SELECT * FROM activity_providers where team_id = 1068;
SELECT * FROM activities a
where crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')
and a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'
)
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by a.id desc;
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 = 1068 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262
SELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;
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 = 882 and sa.provider = 'hubspot';
select * from crm_layouts where crm_configuration_id = 834;
select * from crm_layout_entities where crm_layout_id = 2780;
select * from crm_fields where id IN (321153,321192,321193,321194);
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
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 = 1057 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8
SELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20
SELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10
SELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #
SELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;
select * from users where team_id = 51; # 7783
SELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130
select * from activity_searches where user_id = 7783;
select * from activity_search_filters where activity_search_id IN (32291, 32292);
SELECT asf.activity_search_id, asf.id, asf.value
FROM activity_search_filters asf
WHERE asf.filter = 'group_id'
AND asf.value IN (
SELECT CONCAT(
HEX(SUBSTR(uuid, 5, 4)), '-',
HEX(SUBSTR(uuid, 3, 2)), '-',
HEX(SUBSTR(uuid, 1, 2)), '-',
HEX(SUBSTR(uuid, 9, 2)), '-',
HEX(SUBSTR(uuid, 11))
)
FROM groups
WHERE deleted_at IS NOT NULL
);
SELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where provider = 'hubspot';
SELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133
SELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null
# [PASSWORD_DOTS]
select * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';
select
cp.*
# DISTINCT t.id
# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields
FROM crm_profiles cp
JOIN crm_configurations crm on crm.id = cp.crm_configuration_id
JOIN users u on u.id = cp.user_id
JOIN teams t ON t.id = crm.team_id
WHERE crm.provider = 'salesforce' and t.status = 'active'
and cp.archived_at IS NULL and u.deleted_at IS NULL
and t.id NOT IN (1093)
and t.id = 2
and cp.contact_fields IS NULL;
# and c.crm_provider_id = '003Uu00000ojD4NIAU';
SELECT * FROM users WHERE id = 26484;
SELECT * FROM crm_profiles WHERE user_id = 26484;
SELECT * FROM social_accounts WHERE sociable_id = 26484;
SELECT * FROM crm_configurations where provider = 'salesforce';
select * from users where id IN (10022, 10403);
select * from users where team_id IN (526);
select * from teams where id IN (526, 532);
select * from crm_configurations where id IN (500, 516);
select * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);
select * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';
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 = 526 and sa.provider = 'salesforce';
select * from team_settings where team_id IN (526, 532);
select * from users where id IN (22824);
select * from crm_profiles where crm_configuration_id IN (1026);
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 = 1093 and sa.provider = 'salesforce';
select * from teams where id = 1099;
select * from users where id = 29643
select * from activity_processing_states;
SELECT * FROM teams where name LIKE '%Fare%'; # 233
SELECT * FROM opportunities where crm_configuration_id = 215
# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'
;
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 = 1088 and sa.provider = 'hubspot';
SELECT * FROM teams order by updated_at DESC
SELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account
select * from crm_configurations where provider = 'pipedrive';
select * from teams where id = 957;
select * from crm_configurations where id = 957;
SELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743
SELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;
select * from users where team_id = 1; # 26726 - Gabriela Dureva
SELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific
select * from activities where user_id = 26726 order by id desc;
select * from contacts where crm_configuration_id = 1
and email IN ('[EMAIL]', '[EMAIL]'); # 2094416, 2093620
SELECT * FROM contacts WHERE id = 6284931;
SELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id
WHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;
select * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);
select * from crm_configurations where id = 1;
43801692-1aeb-32ce-acba-5b80a479701a
44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b
405975c0-b3d0-7aaa-821f-09d59cae6dd1
4caf848d-4bed-2299-b248-7788d41f9fca
49bedc3f-f196-eef3-89c3-dea6a3b4aa63
43420989-a09d-b8f8-9806-c8bbf7a02aac
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 = 1 and sa.provider = 'salesforce';
SELECT * FROM activities WHERE id = 75461988;
SELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;
select * from contacts where id = 17900517;
select * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id
where crm.provider != 'salesforce';
select * from users where id = 21047;
SELECT * FROM crm_configurations WHERE id = 892;
SELECT * FROM teams WHERE id = 942;
select * from opportunities where team_id = 942 order by updated_at desc;
select * from contacts where team_id = 942 order by updated_at desc;
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 = 942 and sa.provider = 'hubspot';
SELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430
SELECT * FROM crm_configurations WHERE id = 1;
SELECT * FROM teams WHERE crm_id = 1;
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 = 1 and sa.provider = 'salesforce';
select id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1
SELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430
select * from teams where id = 852;
select * from groups where id = 2286;
select * from sidekick_settings where team_id = 852;
select * from default_activity_types where team_id = 852;
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1 AND u.deleted_at IS NULL
AND u.crm_required = 1
AND u.team_id = 1
ORDER BY u.team_id;
SELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (
18481
);
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1
AND u.deleted_at IS NULL
AND u.crm_required = 1
# AND u.team_id = 1
AND p.id IS NULL -- Move this condition to WHERE clause
ORDER BY u.team_id;
SELECT * FROM opportunities WHERE id = 20002609;
select * from teams where id = 1122; # Velatir, 29953 - [EMAIL]
select * from crm_configurations where id = 1060;
select * from crm_layouts where crm_configuration_id = 1060;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;
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 = 1122 and sa.provider = 'hubspot';
select * from opportunities where team_id = 1122 order by updated_at desc;
select * from crm_field_data where object_type = 'contact';
SELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262
SELECT
CONCAT(u.id, CASE WHEN u.id = t.own...
|
85356
|
NULL
|
NULL
|
NULL
|
|
85361
|
2922
|
10
|
2026-05-28T12:21:17.610615+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970877610_m1.jpg...
|
PhpStorm
|
faVsco.js – HubspotJournalPollingService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileEditViewNavigateCodeRefactorRunToolsGi PhpStormFileEditViewNavigateCodeRefactorRunToolsGitN3WindowHelpec2-user@ip-10-30-129-190:~screenpipe"O ₴4allDOCKER#_####_#####\\###||\#/881DEV (-zsh)O 82-zsh-zshX5ec2-user@ip-10-Amazon Linux 2023 (ECS Optimized)100% C8•BluetoothDevicesLukas's Magic Mousesoundcore AeroClipLakyLak bose qc35 llM720 TriathlonMagic KeyboardMagic KeyboardSoundcore Life Dot 2 NCBluetooth Settings...Thu 28 May 15:21:1774% ₫_m/For documentation, visit [URL_WITH_CREDENTIALS] ~]$ exitlogoutConnection to jiminny-prod-ecsi closed.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ applukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ vprodWarning: Permanently added 'jiminny-prod-ecs1' (ED25519) to the list of known hosts.A newer release of "Amazon Linux" is available.Version 2023.10.20260330:Version 2023.11.20260406:Version 2023.11.20260413:Version2023.11.20260427:Version2023.11.20260505:Version 2023.11.20260509:Version 2023.11.20260511:Version 2023.11.20260514:Run "/usr/bin/dnf check-release-update" for full release and version update info#_~\ ####_\ #####\\###1\#/Amazon Linux 2023 (ECS Optimized)/m/'For documentation, visit [URL_WITH_CREDENTIALS] ~]$ client_loop: send disconnect: Broken pipeukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-relay) $I...
|
NULL
|
5814379487936722865
|
NULL
|
click
|
ocr
|
NULL
|
PhpStormFileEditViewNavigateCodeRefactorRunToolsGi PhpStormFileEditViewNavigateCodeRefactorRunToolsGitN3WindowHelpec2-user@ip-10-30-129-190:~screenpipe"O ₴4allDOCKER#_####_#####\\###||\#/881DEV (-zsh)O 82-zsh-zshX5ec2-user@ip-10-Amazon Linux 2023 (ECS Optimized)100% C8•BluetoothDevicesLukas's Magic Mousesoundcore AeroClipLakyLak bose qc35 llM720 TriathlonMagic KeyboardMagic KeyboardSoundcore Life Dot 2 NCBluetooth Settings...Thu 28 May 15:21:1774% ₫_m/For documentation, visit [URL_WITH_CREDENTIALS] ~]$ exitlogoutConnection to jiminny-prod-ecsi closed.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ applukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ vprodWarning: Permanently added 'jiminny-prod-ecs1' (ED25519) to the list of known hosts.A newer release of "Amazon Linux" is available.Version 2023.10.20260330:Version 2023.11.20260406:Version 2023.11.20260413:Version2023.11.20260427:Version2023.11.20260505:Version 2023.11.20260509:Version 2023.11.20260511:Version 2023.11.20260514:Run "/usr/bin/dnf check-release-update" for full release and version update info#_~\ ####_\ #####\\###1\#/Amazon Linux 2023 (ECS Optimized)/m/'For documentation, visit [URL_WITH_CREDENTIALS] ~]$ client_loop: send disconnect: Broken pipeukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-relay) $I...
|
85360
|
NULL
|
NULL
|
NULL
|
|
85360
|
2922
|
9
|
2026-05-28T12:21:08.693199+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970868693_m1.jpg...
|
Control Centre
|
Control Centre
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Bluetooth
Bluetooth
Devices
Lukas’s Magic Mouse, 7 Bluetooth
Bluetooth
Devices
Lukas’s Magic Mouse, 74%
soundcore AeroClip, 80%
LakyLak bose qc35 II
M720 Triathlon
Magic Keyboard
Magic Keyboard
Soundcore Life Dot 2 NC
Bluetooth Settings…...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"Bluetooth","depth":2,"bounds":{"left":0.7916667,"top":0.04777778,"width":0.042708334,"height":0.017777778},"on_screen":true,"automation_id":"bluetooth-header","role_description":"text"},{"role":"AXCheckBox","text":"Bluetooth","depth":2,"bounds":{"left":0.9527778,"top":0.044444446,"width":0.02638889,"height":0.024444444},"on_screen":true,"automation_id":"bluetooth-header","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false},{"role":"AXHeading","text":"Devices","depth":3,"bounds":{"left":0.7916667,"top":0.090555556,"width":0.032291666,"height":0.016666668},"on_screen":true,"role_description":"heading"},{"role":"AXCheckBox","text":"Lukas’s Magic Mouse, 74%","depth":3,"bounds":{"left":0.78541666,"top":0.11111111,"width":0.2,"height":0.035555556},"on_screen":true,"automation_id":"bluetooth-device-Lukas’s Magic Mouse","role_description":"toggle button","subrole":"AXToggle","is_enabled":true},{"role":"AXCheckBox","text":"soundcore AeroClip, 80%","depth":3,"bounds":{"left":0.78541666,"top":0.14666666,"width":0.2,"height":0.035555556},"on_screen":true,"automation_id":"bluetooth-device-soundcore AeroClip","role_description":"toggle button","subrole":"AXToggle","is_enabled":true},{"role":"AXCheckBox","text":"LakyLak bose qc35 II","depth":3,"bounds":{"left":0.78541666,"top":0.18222222,"width":0.2,"height":0.035555556},"on_screen":true,"automation_id":"bluetooth-device-LakyLak bose qc35 II","role_description":"toggle button","subrole":"AXToggle","is_enabled":true},{"role":"AXCheckBox","text":"M720 Triathlon","depth":3,"bounds":{"left":0.78541666,"top":0.21777777,"width":0.2,"height":0.035555556},"on_screen":true,"automation_id":"bluetooth-device-M720 Triathlon","role_description":"toggle button","subrole":"AXToggle","is_enabled":false},{"role":"AXCheckBox","text":"Magic Keyboard","depth":3,"bounds":{"left":0.78541666,"top":0.25333333,"width":0.2,"height":0.035555556},"on_screen":true,"automation_id":"bluetooth-device-Magic Keyboard","role_description":"toggle button","subrole":"AXToggle","is_enabled":true},{"role":"AXCheckBox","text":"Magic Keyboard","depth":3,"bounds":{"left":0.78541666,"top":0.2888889,"width":0.2,"height":0.035555556},"on_screen":true,"automation_id":"bluetooth-device-Magic Keyboard","role_description":"toggle button","subrole":"AXToggle","is_enabled":true},{"role":"AXCheckBox","text":"Soundcore Life Dot 2 NC","depth":3,"bounds":{"left":0.78541666,"top":0.32444444,"width":0.2,"height":0.035555556},"on_screen":true,"automation_id":"bluetooth-device-Soundcore Life Dot 2 NC","role_description":"toggle button","subrole":"AXToggle","is_enabled":true},{"role":"AXButton","text":"Bluetooth Settings…","depth":2,"bounds":{"left":0.78541666,"top":0.37555555,"width":0.09791667,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false}]...
|
-4635391570997941161
|
-3732869420107362620
|
click
|
hybrid
|
NULL
|
Bluetooth
Bluetooth
Devices
Lukas’s Magic Mouse, 7 Bluetooth
Bluetooth
Devices
Lukas’s Magic Mouse, 74%
soundcore AeroClip, 80%
LakyLak bose qc35 II
M720 Triathlon
Magic Keyboard
Magic Keyboard
Soundcore Life Dot 2 NC
Bluetooth Settings…
PhpStormFileEditViewNavigateCodeRefactorRunToolsGitN3WindowHelpec2-user@ip-10-30-129-190:~screenpipe"allDOCKER#_####_#####\\###||\#/881DEV (-zsh)O 82-zsh-zshX5ec2-user@ip-10-Amazon Linux 2023 (ECS Optimized)100% C78•BluetoothDevicesLukas's Magic Mousesoundcore AeroClipLakyLak bose qc35 llM720 TriathlonMagic KeyboardMagic KeyboardSoundcore Life Dot 2 NCBluetooth Settings...Thu 28 May 15:21:0874% ₫80%•_m/For documentation, visit [URL_WITH_CREDENTIALS] ~]$ exitlogoutConnection to jiminny-prod-ecsi closed.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ applukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ vprodWarning: Permanently added 'jiminny-prod-ecs1' (ED25519) to the list of known hosts.A newer release of "Amazon Linux" is available.Version 2023.10.20260330:Version 2023.11.20260406:Version 2023.11.20260413:Version2023.11.20260427:Version2023.11.20260505:Version 2023.11.20260509:Version 2023.11.20260511:Version 2023.11.20260514:Run "/usr/bin/dnf check-release-update" for full release and version update info#_~\ ####_\ #####\\###1\#/Amazon Linux 2023 (ECS Optimized)/m/'For documentation, visit [URL_WITH_CREDENTIALS] ~]$ client_loop: send disconnect: Broken pipeukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-rejay) $D...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85359
|
2922
|
8
|
2026-05-28T12:21:07.195934+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970867195_m1.jpg...
|
Control Centre
|
Control Centre
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileEditViewNavigateCodeRefactorRunDOCKER# PhpStormFileEditViewNavigateCodeRefactorRunDOCKER#_####_#####\\###||\#/881DEV (-zsh)O 82-zshToolsGitN3WindowHelpec2-user@ip-10-30-129-190:~screenpipe"€ 884-zshX5allec2-user@ip-10-30100% C78•Thu 28 May 15:21:07iTermAmazon Linux 2023 (ECS Optimized)Wi-FiOffBluetoothOnAirDropOffDisplaySoundFOCusStageManagerScreenMirroring_m/For documentation, visit [URL_WITH_CREDENTIALS] ~]$ exitlogoutConnection to jiminny-prod-ecsi closed.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ applukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ vprodWarning: Permanently added 'jiminny-prod-ecs1' (ED25519) to the list of known hosts.A newer release of "Amazon Linux" is available.Version 2023.10.20260330:Version 2023.11.20260406:Version 2023.11.20260413:Version2023.11.20260427:Version2023.11.20260505:Version 2023.11.20260509:Version 2023.11.20260511:Version 2023.11.20260514:Run "/usr/bin/dnf check-release-update" for full release and version update info#_~\ ####_\ #####\\###1\#/Amazon Linux 2023 (ECS Optimized)Music.app/m/'For documentation, visit [URL_WITH_CREDENTIALS] ~]$ client_loop: send disconnect: Broken pipeukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-rejay) $D...
|
NULL
|
4867313317977352852
|
NULL
|
click
|
ocr
|
NULL
|
PhpStormFileEditViewNavigateCodeRefactorRunDOCKER# PhpStormFileEditViewNavigateCodeRefactorRunDOCKER#_####_#####\\###||\#/881DEV (-zsh)O 82-zshToolsGitN3WindowHelpec2-user@ip-10-30-129-190:~screenpipe"€ 884-zshX5allec2-user@ip-10-30100% C78•Thu 28 May 15:21:07iTermAmazon Linux 2023 (ECS Optimized)Wi-FiOffBluetoothOnAirDropOffDisplaySoundFOCusStageManagerScreenMirroring_m/For documentation, visit [URL_WITH_CREDENTIALS] ~]$ exitlogoutConnection to jiminny-prod-ecsi closed.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ applukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ vprodWarning: Permanently added 'jiminny-prod-ecs1' (ED25519) to the list of known hosts.A newer release of "Amazon Linux" is available.Version 2023.10.20260330:Version 2023.11.20260406:Version 2023.11.20260413:Version2023.11.20260427:Version2023.11.20260505:Version 2023.11.20260509:Version 2023.11.20260511:Version 2023.11.20260514:Run "/usr/bin/dnf check-release-update" for full release and version update info#_~\ ####_\ #####\\###1\#/Amazon Linux 2023 (ECS Optimized)Music.app/m/'For documentation, visit [URL_WITH_CREDENTIALS] ~]$ client_loop: send disconnect: Broken pipeukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-rejay) $D...
|
85358
|
NULL
|
NULL
|
NULL
|
|
85358
|
2922
|
7
|
2026-05-28T12:21:06.601179+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970866601_m1.jpg...
|
Control Centre
|
Control Centre
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Wi‑Fi
Focus
Bluetooth
AirDrop
Stage Manager
Screen Wi‑Fi
Focus
Bluetooth
AirDrop
Stage Manager
Screen Mirroring
Display
Sound
Airplay Audio
Music.app
play
next...
|
[{"role":"AXCheckBox","text [{"role":"AXCheckBox","text":"Wi‑Fi","depth":2,"bounds":{"left":0.79583335,"top":0.093888886,"width":0.093055554,"height":0.045555554},"on_screen":true,"automation_id":"controlcenter-wifi","role_description":"toggle button","subrole":"AXToggle","is_enabled":true},{"role":"AXCheckBox","text":"Focus","depth":2,"bounds":{"left":0.8958333,"top":0.086666666,"width":0.093055554,"height":0.06888889},"on_screen":true,"automation_id":"controlcenter-focus-modes","role_description":"toggle button","subrole":"AXToggle","is_enabled":true},{"role":"AXCheckBox","text":"Bluetooth","depth":2,"bounds":{"left":0.79583335,"top":0.13833334,"width":0.0875,"height":0.045555554},"on_screen":true,"automation_id":"controlcenter-bluetooth","role_description":"toggle button","subrole":"AXToggle","is_enabled":true},{"role":"AXCheckBox","text":"AirDrop","depth":2,"bounds":{"left":0.79583335,"top":0.18277778,"width":0.093055554,"height":0.045555554},"on_screen":true,"automation_id":"controlcenter-airdrop","role_description":"toggle button","subrole":"AXToggle","is_enabled":true},{"role":"AXButton","text":"Stage Manager","depth":2,"bounds":{"left":0.90260416,"top":0.17722222,"width":0.029513888,"height":0.050555557},"on_screen":true,"automation_id":"controlcenter-stagemanager","role_description":"toggle button","subrole":"AXToggle","is_enabled":true},{"role":"AXButton","text":"Screen Mirroring","depth":2,"bounds":{"left":0.9458333,"top":0.16666667,"width":0.043055557,"height":0.06888889},"on_screen":true,"automation_id":"controlcenter-screen-mirroring","role_description":"toggle button","subrole":"AXToggle","is_enabled":true},{"role":"AXStaticText","text":"Display","depth":3,"bounds":{"left":0.80277777,"top":0.25388888,"width":0.029513888,"height":0.016666668},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Sound","depth":2,"bounds":{"left":0.80277777,"top":0.3338889,"width":0.025694445,"height":0.016666668},"on_screen":true,"automation_id":"controlcenter-volume","role_description":"text"},{"role":"AXCheckBox","text":"Airplay Audio","depth":2,"bounds":{"left":0.9638889,"top":0.35555556,"width":0.018055556,"height":0.028888889},"on_screen":true,"automation_id":"controlcenter-airplay","role_description":"toggle button","subrole":"AXToggle","is_enabled":true},{"role":"AXStaticText","text":"Music.app","depth":2,"bounds":{"left":0.8375,"top":0.4327778,"width":0.041666668,"height":0.016666668},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"play","depth":2,"bounds":{"left":0.9458333,"top":0.42666668,"width":0.018055556,"height":0.028888889},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"next","depth":2,"bounds":{"left":0.9638889,"top":0.42666668,"width":0.018055556,"height":0.028888889},"on_screen":true,"role_description":"button","is_enabled":false}]...
|
-8871609275972961978
|
9172942166022350908
|
visual_change
|
hybrid
|
NULL
|
Wi‑Fi
Focus
Bluetooth
AirDrop
Stage Manager
Screen Wi‑Fi
Focus
Bluetooth
AirDrop
Stage Manager
Screen Mirroring
Display
Sound
Airplay Audio
Music.app
play
next
PhpStormFileEditViewNavigateCodeRefactorRunDOCKER#_####_#####\\###||\#/881DEV (-zsh)O 82-zshToolsGitN3WindowHelpec2-user@ip-10-30-129-190:~screenpipe"84-zshX5allec2-user@ip-10-30100% C78•Thu 28 May 15:21:06iTermAmazon Linux 2023 (ECS Optimized)Wi-FiOffBluetoothOnAirDropOffDisplaySoundFOCusStageManagerScreenMirroring_m/For documentation, visit [URL_WITH_CREDENTIALS] ~]$ exitlogoutConnection to jiminny-prod-ecsi closed.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ applukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ vprodWarning: Permanently added 'jiminny-prod-ecs1' (ED25519) to the list of known hosts.A newer release of "Amazon Linux" is available.Version 2023.10.20260330:Version 2023.11.20260406:Version 2023.11.20260413:Version2023.11.20260427:Version2023.11.20260505:Version 2023.11.20260509:Version 2023.11.20260511:Version 2023.11.20260514:Run "/usr/bin/dnf check-release-update" for full release and version update info#_~\ ####_\ #####\\###1\#/Amazon Linux 2023 (ECS Optimized)Music.app/m/'For documentation, visit [URL_WITH_CREDENTIALS] ~]$ client_loop: send disconnect: Broken pipeukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-rejay) $D...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85357
|
2922
|
6
|
2026-05-28T12:21:05.909709+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970865909_m1.jpg...
|
PhpStorm
|
faVsco.js – HubspotJournalPollingService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions...
|
[{"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":"#12121 on JY-20963-fix-import-on-deleted-entity, menu","depth":5,"on_screen":true,"help_text":"Pull request #12121 exists for current branch JY-20963-fix-import-on-deleted-entity","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":"TextRelayServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","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}]...
|
1962564511686315741
|
1735522336325714144
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
PhpStormFileEditViewNavigateCodeRefactorRunToolsGitWindowHelpec2-user@ip-10-30-129-190:~screenpipe"X4lahlA100% CThu 28 May 15:21:05181ec2-user@ip-10-30-140-...₴7DOCKER#_####_#####\\###||\#/881DEV (-zsh)O [EMAIL] Linux 2023 (ECS Optimized)_m/For documentation, visit [URL_WITH_CREDENTIALS] ~]$ exitlogoutConnection to jiminny-prod-ecsi closed.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ applukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ vprodWarning: Permanently added 'jiminny-prod-ecs1' (ED25519) to the list of known hosts.A newer release of "Amazon Linux" is available.Version 2023.10.20260330:Version 2023.11.20260406:Version 2023.11.20260413:Version2023.11.20260427:Version2023.11.20260505:Version 2023.11.20260509:Version 2023.11.20260511:Version 2023.11.20260514:Run "/usr/bin/dnf check-release-update" for full release and version update info#_~\ ####_\ #####\\###1\#/Amazon Linux 2023 (ECS Optimized)/m/'For documentation, visit [URL_WITH_CREDENTIALS] ~]$ client_loop: send disconnect: Broken pipeukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-rejay) $D...
|
85355
|
NULL
|
NULL
|
NULL
|
|
85356
|
2923
|
8
|
2026-05-28T12:21:01.097693+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970861097_m2.jpg...
|
PhpStorm
|
faVsco.js – HubspotJournalPollingService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
sluckcolVIChRotonWindowHelp= github.com/~ Google G sluckcolVIChRotonWindowHelp= github.com/~ Google Gemini© Pipelines - |Iminnylapp* (JY-20613) Allow owner's role to tAxUemered tines on New codJY-20963 fix deleted object impsJiminny\Exceptions(EmailActivityfrM'inbox (1734) - Aukas.kovallk@jlminoonye Rosd ve php 81.15 dedPlatform Sorint 5.02 - Platform Te8 Jiminny7 (SRD-6881] (On demand) TransSona Strbramaal an at 27/05/2021O Illyana Netsova at 27/05/2026, 18Jy 20910 schedule parallel up: XGemini 3.1 Pro ~That is a very common misconception, but it is not true anymoreWhile it is true that many years ago (around 2014-2016) phpredis had some bugs regarding theorder in which EX and NX arguments were passed, it has fully supported combining them into asingle atomic command for a very long time.To do this atomically using the phpredis driver, you simply pass an associative array of options asthe third argumentHere is exactly how you would write it in your code:PHPSlockKey = sprintf(self::LOCK_KEY, $this->updateTarget);1/ This executes a single, atomic SET conand with both NX and EXSacquired = Redis::set(slockkey, true, L'NX, "EX => seLf::LOCK.TTL-SECONDSJ)8if (! Sacquired) €// Another process already holds the locknetuyn:/1 ... report your metrics …..By relying on separate (get, set, and expire commands, you are leaving a microsecond gapopen. In your new multi-process Supervisor architecture, two parallel workers can check the get,both see (null, and both proceed to write the lock and push duplicate metrics. Using the arraysyntax above closes that gap completely.A Cleaner Laravel AlternativeSince vou are inside a Laravel app, you don't even need to fight with driver-specific Redis syntax.Ask GeminilYour lim nou chate trhn" uend to imnroy our moddle Camioiie 4l1t cao mata mtake co donbls chack " Yoir oracy & CaminSummarize pageI1 OpenJy 20910 schedule paraliCFilter tec.~ Б app~ fi Component/ESEntityTypeReindexer.phpReindexTargetStatsReporter,p...UpdateProcessManager.phpv Consolev # Commands/ElasticsearchAsyncUpdateEsEntities.php•1AsvncUodateSupervisor.ohoDeleteEmailDocumentsCom..ReindexEntityTypes.php•1 RemoveGhostParticipantsCo..ResetAsyncElasticSearchCo...® Kernel.php~ # Contracts/ESStopSignallnterface.php~ Ei Traits2) GracefullvStonoable.ohov # tests/Unit/Component/ESEntityTypeReindexerTest.phpReindexTargetStatsReporterTes…UpdateProcessManagerTest.phpHoeActivityLaterMoneiJiminnY ….eUnreos@ Threads6 Huddles* Drafts & sent® Directories01Ab External connections* Starred8 platform-backend-8 olatform-inner-team(6) Channelsshethante# alerts# backend# bugs# confusion-clinic# donut_time# engineering# general# happy_birthday8 infosec_internal_all# infra-changes# infrastructure_dev# jbu-team-info# jiminny-bg# platform-team# platform-tickets# product_launchessrandom# releases# support# thank-yous# the_people_of_jimi.a Direct messages#. Vasil VasilevF8. Stefka Stoyanova(T. lliyana Netseva8?. Stoyan TomovPetko KashinskiInu co moy tocrueQ Describe what you are looking for* {L.lliyana NetsevaNoR0d canvislliyana Netseva•Tnis conversaton is just derween elliyana Neseva and you, cneck oue their pronle to learn more aboud tnem.View Profilewtkas Kovalnk wxy pvздравей, имаш ли минуткаivana Nerseva 2-54PMokolo 4 chasa svoboden ln shte siWKas KoValkephyrздрасти, да но мисля че няма нужда вечечухме се сьс Стоян и тои оеше запознат и по този кеиспол този кейс имам предвил httos://nimiaoustascian.nat/brouce/CO0.6881Jira Cloud +** lue se sel I ia crition in saved search disappearsReady for customera Lukas Kovalik (you)eottodmatyikeyвиж коментари и ако тодова нещо дрчго може дя се умемwiyana Neseva 7:5o PMpopravi me ako greshaLukas Kovalik 2:57PMvidgh kakvo si napisal, neshtoto koeto mi e stranno e che razlikata mejdu tova koeto pravi ta i tova koeto si napisal ti e natiskaneto na enter-a za da izpulni search-aда се търси по transcriptlliyana Netseva 3:04 PMima li prichina tova da e napraveno po tozi nachin ili prosto taka e napraveno nqkogaukac Kowaik aciorНА ЭНАМ КОМПОЗИТЕН ПИЛТКЯ А МОЖА ВИ Т0968Я ЛАТСИ НАСТООМШІ НеЦІКТИТИ ПОСЛА ЛА НАСЛИВИШ АЛНЯ ЗА9ЯКА А СКАЛТИМ НаПЛИМетука не сьм сигурен, по-скоро е зи поолукаMessage Iliyana Netseva+ Aa...
|
NULL
|
5088817470014102170
|
NULL
|
click
|
ocr
|
NULL
|
sluckcolVIChRotonWindowHelp= github.com/~ Google G sluckcolVIChRotonWindowHelp= github.com/~ Google Gemini© Pipelines - |Iminnylapp* (JY-20613) Allow owner's role to tAxUemered tines on New codJY-20963 fix deleted object impsJiminny\Exceptions(EmailActivityfrM'inbox (1734) - Aukas.kovallk@jlminoonye Rosd ve php 81.15 dedPlatform Sorint 5.02 - Platform Te8 Jiminny7 (SRD-6881] (On demand) TransSona Strbramaal an at 27/05/2021O Illyana Netsova at 27/05/2026, 18Jy 20910 schedule parallel up: XGemini 3.1 Pro ~That is a very common misconception, but it is not true anymoreWhile it is true that many years ago (around 2014-2016) phpredis had some bugs regarding theorder in which EX and NX arguments were passed, it has fully supported combining them into asingle atomic command for a very long time.To do this atomically using the phpredis driver, you simply pass an associative array of options asthe third argumentHere is exactly how you would write it in your code:PHPSlockKey = sprintf(self::LOCK_KEY, $this->updateTarget);1/ This executes a single, atomic SET conand with both NX and EXSacquired = Redis::set(slockkey, true, L'NX, "EX => seLf::LOCK.TTL-SECONDSJ)8if (! Sacquired) €// Another process already holds the locknetuyn:/1 ... report your metrics …..By relying on separate (get, set, and expire commands, you are leaving a microsecond gapopen. In your new multi-process Supervisor architecture, two parallel workers can check the get,both see (null, and both proceed to write the lock and push duplicate metrics. Using the arraysyntax above closes that gap completely.A Cleaner Laravel AlternativeSince vou are inside a Laravel app, you don't even need to fight with driver-specific Redis syntax.Ask GeminilYour lim nou chate trhn" uend to imnroy our moddle Camioiie 4l1t cao mata mtake co donbls chack " Yoir oracy & CaminSummarize pageI1 OpenJy 20910 schedule paraliCFilter tec.~ Б app~ fi Component/ESEntityTypeReindexer.phpReindexTargetStatsReporter,p...UpdateProcessManager.phpv Consolev # Commands/ElasticsearchAsyncUpdateEsEntities.php•1AsvncUodateSupervisor.ohoDeleteEmailDocumentsCom..ReindexEntityTypes.php•1 RemoveGhostParticipantsCo..ResetAsyncElasticSearchCo...® Kernel.php~ # Contracts/ESStopSignallnterface.php~ Ei Traits2) GracefullvStonoable.ohov # tests/Unit/Component/ESEntityTypeReindexerTest.phpReindexTargetStatsReporterTes…UpdateProcessManagerTest.phpHoeActivityLaterMoneiJiminnY ….eUnreos@ Threads6 Huddles* Drafts & sent® Directories01Ab External connections* Starred8 platform-backend-8 olatform-inner-team(6) Channelsshethante# alerts# backend# bugs# confusion-clinic# donut_time# engineering# general# happy_birthday8 infosec_internal_all# infra-changes# infrastructure_dev# jbu-team-info# jiminny-bg# platform-team# platform-tickets# product_launchessrandom# releases# support# thank-yous# the_people_of_jimi.a Direct messages#. Vasil VasilevF8. Stefka Stoyanova(T. lliyana Netseva8?. Stoyan TomovPetko KashinskiInu co moy tocrueQ Describe what you are looking for* {L.lliyana NetsevaNoR0d canvislliyana Netseva•Tnis conversaton is just derween elliyana Neseva and you, cneck oue their pronle to learn more aboud tnem.View Profilewtkas Kovalnk wxy pvздравей, имаш ли минуткаivana Nerseva 2-54PMokolo 4 chasa svoboden ln shte siWKas KoValkephyrздрасти, да но мисля че няма нужда вечечухме се сьс Стоян и тои оеше запознат и по този кеиспол този кейс имам предвил httos://nimiaoustascian.nat/brouce/CO0.6881Jira Cloud +** lue se sel I ia crition in saved search disappearsReady for customera Lukas Kovalik (you)eottodmatyikeyвиж коментари и ако тодова нещо дрчго може дя се умемwiyana Neseva 7:5o PMpopravi me ako greshaLukas Kovalik 2:57PMvidgh kakvo si napisal, neshtoto koeto mi e stranno e che razlikata mejdu tova koeto pravi ta i tova koeto si napisal ti e natiskaneto na enter-a za da izpulni search-aда се търси по transcriptlliyana Netseva 3:04 PMima li prichina tova da e napraveno po tozi nachin ili prosto taka e napraveno nqkogaukac Kowaik aciorНА ЭНАМ КОМПОЗИТЕН ПИЛТКЯ А МОЖА ВИ Т0968Я ЛАТСИ НАСТООМШІ НеЦІКТИТИ ПОСЛА ЛА НАСЛИВИШ АЛНЯ ЗА9ЯКА А СКАЛТИМ НаПЛИМетука не сьм сигурен, по-скоро е зи поолукаMessage Iliyana Netseva+ Aa...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85355
|
2922
|
5
|
2026-05-28T12:21:00.971245+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970860971_m1.jpg...
|
PhpStorm
|
faVsco.js – HubspotJournalPollingService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileEditViewNavigateCodeRefactorRunToolsGi PhpStormFileEditViewNavigateCodeRefactorRunToolsGitWindowHelpec2-user@ip-10-30-129-190:~screenpipe"-84‹$0(ah]БГ100% <78 • Thu 28 May 15:21:00181ec2-user@ip-10-30-140-...₴7DOCKER#_####_#####\\###||\#/V~'881DEV (-zsh)O [EMAIL] Linux 2023 (ECS Optimized)_/m/For documentation, visit [URL_WITH_CREDENTIALS] ~]$ exitlogoutConnection to jiminny-prod-ecsi closed.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ applukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ vprodWarning: Permanently added 'jiminny-prod-ecs1' (ED25519) to the list of known hosts.A newer release of "Amazon Linux" is available.Version 2023.10.20260330:Version 2023.11.20260406:Version 2023.11.20260413:Version2023.11.20260427:Version2023.11.20260505:Version 2023.11.20260509:Version 2023.11.20260511:Version 2023.11.20260514:Run "/usr/bin/dnf check-release-update" for full release and version update info#_~\ ####_\ #####\\###1\#/Amazon Linux 2023 (ECS Optimized)/m/'For documentation, visit [URL_WITH_CREDENTIALS] ~]$ client_loop: send disconnect: Broken pipeukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-relay) $I...
|
NULL
|
659854332667743518
|
NULL
|
click
|
ocr
|
NULL
|
PhpStormFileEditViewNavigateCodeRefactorRunToolsGi PhpStormFileEditViewNavigateCodeRefactorRunToolsGitWindowHelpec2-user@ip-10-30-129-190:~screenpipe"-84‹$0(ah]БГ100% <78 • Thu 28 May 15:21:00181ec2-user@ip-10-30-140-...₴7DOCKER#_####_#####\\###||\#/V~'881DEV (-zsh)O [EMAIL] Linux 2023 (ECS Optimized)_/m/For documentation, visit [URL_WITH_CREDENTIALS] ~]$ exitlogoutConnection to jiminny-prod-ecsi closed.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ applukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ vprodWarning: Permanently added 'jiminny-prod-ecs1' (ED25519) to the list of known hosts.A newer release of "Amazon Linux" is available.Version 2023.10.20260330:Version 2023.11.20260406:Version 2023.11.20260413:Version2023.11.20260427:Version2023.11.20260505:Version 2023.11.20260509:Version 2023.11.20260511:Version 2023.11.20260514:Run "/usr/bin/dnf check-release-update" for full release and version update info#_~\ ####_\ #####\\###1\#/Amazon Linux 2023 (ECS Optimized)/m/'For documentation, visit [URL_WITH_CREDENTIALS] ~]$ client_loop: send disconnect: Broken pipeukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-relay) $I...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85354
|
2923
|
7
|
2026-05-28T12:20:51.357367+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970851357_m2.jpg...
|
Slack
|
Iliyana Netseva (DM) - Jiminny Inc - 2 new items - Iliyana Netseva (DM) - Jiminny Inc - 2 new items - Slack...
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
Stoyan Tomov
Petko Kashinski
Galya Dimitrova
Todor Stamatov
Steliyan Georgiev
Ves
Mira
Nikolay Yankov
James Graham
Lukas Kovalik
you
Jira Cloud
Toast
Google Calendar
Messages
Messages
Add canvas
Add canvas
Add and Edit Channel Tabs
Canvas
List
Folder
Iliyana Netseva
Iliyana Netseva
This conversation is just between
@Iliyana Netseva
@Iliyana Netseva
and you. Check out their profile to learn more about them.
View Profile
Jump to date
Lukas Kovalik
Today at 12:32:31 PM
12:32 PM
здравей, имаш ли минутка
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Iliyana Netseva
Today at 2:51:31 PM
2:51 PM
okolo 4 chasa svoboden li shte si ?
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Lukas Kovalik
Today at 2:52:21 PM
2:52 PM
здрасти, да но мисля че няма нужда вече
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Today at 2:52:47 PM
2:52
чухме се със Стоян и той беше запознат и по този кейс
React with white_check_mark
React with eyes
React with raised_hands...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"bounds":{"left":0.5056516,"top":0.058260176,"width":0.011968086,"height":0.028731046},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"bounds":{"left":0.50299203,"top":0.10055866,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":8,"bounds":{"left":0.50664896,"top":0.13806863,"width":0.009973404,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":6,"bounds":{"left":0.50299203,"top":0.15482841,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":8,"bounds":{"left":0.50764626,"top":0.19233839,"width":0.007978723,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":6,"bounds":{"left":0.50299203,"top":0.20909816,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":8,"bounds":{"left":0.5053192,"top":0.24660814,"width":0.012965426,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5053192,"top":0.24660814,"width":0.0026595744,"height":0.011173184}},{"char_start":1,"char_count":7,"bounds":{"left":0.50764626,"top":0.24660814,"width":0.010638298,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":6,"bounds":{"left":0.50299203,"top":0.26336792,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":8,"bounds":{"left":0.50797874,"top":0.3008779,"width":0.0076462766,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50797874,"top":0.3008779,"width":0.0019946808,"height":0.011173184}},{"char_start":1,"char_count":4,"bounds":{"left":0.5099734,"top":0.3008779,"width":0.0056515955,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":6,"bounds":{"left":0.50299203,"top":0.31763768,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":8,"bounds":{"left":0.50731385,"top":0.35514766,"width":0.008643617,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50731385,"top":0.35514766,"width":0.0019946808,"height":0.011173184}},{"char_start":1,"char_count":4,"bounds":{"left":0.5093085,"top":0.35514766,"width":0.0066489363,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":6,"bounds":{"left":0.50299203,"top":0.3719074,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":8,"bounds":{"left":0.50731385,"top":0.4094174,"width":0.008976064,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50731385,"top":0.4094174,"width":0.0033244682,"height":0.011173184}},{"char_start":1,"char_count":3,"bounds":{"left":0.5106383,"top":0.4094174,"width":0.0056515955,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":13,"bounds":{"left":0.5369016,"top":0.10055866,"width":0.018949468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5369016,"top":0.10055866,"width":0.003656915,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.5405585,"top":0.10055866,"width":0.015292553,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":13,"bounds":{"left":0.5369016,"top":0.12290503,"width":0.01761968,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5369016,"top":0.12290503,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.5398936,"top":0.12290503,"width":0.01462766,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":13,"bounds":{"left":0.5369016,"top":0.1452514,"width":0.018284574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5369016,"top":0.1452514,"width":0.0039893617,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54089093,"top":0.1452514,"width":0.014295213,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":13,"bounds":{"left":0.5369016,"top":0.16759777,"width":0.02925532,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5369016,"top":0.16759777,"width":0.003656915,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.5405585,"top":0.16759777,"width":0.025265958,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"1","depth":13,"bounds":{"left":0.5884308,"top":0.16839585,"width":0.0026595744,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":13,"bounds":{"left":0.5369016,"top":0.18994413,"width":0.024268618,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5369016,"top":0.18994413,"width":0.0039893617,"height":0.014365523}},{"char_start":1,"char_count":10,"bounds":{"left":0.54089093,"top":0.18994413,"width":0.020279255,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"platform-backend-engineers","depth":15,"bounds":{"left":0.5422208,"top":0.28411812,"width":0.044215426,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.28411812,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":25,"bounds":{"left":0.54521275,"top":0.28411812,"width":0.058843084,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":15,"bounds":{"left":0.5422208,"top":0.3064645,"width":0.04454787,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.3064645,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":18,"bounds":{"left":0.54521275,"top":0.3064645,"width":0.04155585,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":15,"bounds":{"left":0.5422208,"top":0.35913807,"width":0.022273935,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.35913807,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54488033,"top":0.35913807,"width":0.019614361,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":15,"bounds":{"left":0.5422208,"top":0.38148445,"width":0.012300532,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.38148445,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":5,"bounds":{"left":0.54488033,"top":0.38148445,"width":0.00930851,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":15,"bounds":{"left":0.5422208,"top":0.4038308,"width":0.018284574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.4038308,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54521275,"top":0.4038308,"width":0.015292553,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"bugs","depth":15,"bounds":{"left":0.5422208,"top":0.42617717,"width":0.010638298,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.42617717,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":3,"bounds":{"left":0.54521275,"top":0.42617717,"width":0.00731383,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":15,"bounds":{"left":0.5422208,"top":0.44852355,"width":0.034574468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.44852355,"width":0.0023271276,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54454786,"top":0.44852355,"width":0.032247342,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"donut_time","depth":15,"bounds":{"left":0.5422208,"top":0.4708699,"width":0.025265958,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.4708699,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54521275,"top":0.4708699,"width":0.022273935,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":15,"bounds":{"left":0.5422208,"top":0.49321628,"width":0.025930852,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.49321628,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":10,"bounds":{"left":0.54488033,"top":0.49321628,"width":0.022938829,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"general","depth":15,"bounds":{"left":0.5422208,"top":0.51556265,"width":0.016289894,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.51556265,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54488033,"top":0.51556265,"width":0.013297873,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"happy_birthday","depth":15,"bounds":{"left":0.5422208,"top":0.53790903,"width":0.033909574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.53790903,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":13,"bounds":{"left":0.54521275,"top":0.53790903,"width":0.030917553,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"infosec_internal_all","depth":15,"bounds":{"left":0.5422208,"top":0.5602554,"width":0.041888297,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.5602554,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":19,"bounds":{"left":0.54355055,"top":0.5602554,"width":0.040226065,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"infra-changes","depth":15,"bounds":{"left":0.5422208,"top":0.5826017,"width":0.029587766,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.5826017,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54355055,"top":0.5826017,"width":0.02825798,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"infrastructure_dev","depth":15,"bounds":{"left":0.5422208,"top":0.6049481,"width":0.040226065,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.6049481,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":17,"bounds":{"left":0.54355055,"top":0.6049481,"width":0.038896278,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"jbu-team-info","depth":15,"bounds":{"left":0.5422208,"top":0.6272945,"width":0.029920213,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.6272945,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54355055,"top":0.6272945,"width":0.028590426,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":15,"bounds":{"left":0.5422208,"top":0.64964086,"width":0.023271276,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.64964086,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54355055,"top":0.64964086,"width":0.021609042,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"platform-team","depth":15,"bounds":{"left":0.5422208,"top":0.67198724,"width":0.03158245,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.67198724,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54521275,"top":0.67198724,"width":0.028590426,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":15,"bounds":{"left":0.5422208,"top":0.6943336,"width":0.034906916,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.6943336,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54521275,"top":0.6943336,"width":0.031914894,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":15,"bounds":{"left":0.5422208,"top":0.71668,"width":0.03856383,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.71668,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54521275,"top":0.71668,"width":0.03557181,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"random","depth":15,"bounds":{"left":0.5422208,"top":0.7390263,"width":0.016954787,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.7390263,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":5,"bounds":{"left":0.54421544,"top":0.7390263,"width":0.014960106,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":15,"bounds":{"left":0.5422208,"top":0.7613727,"width":0.018284574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.7613727,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":7,"bounds":{"left":0.54421544,"top":0.7613727,"width":0.016289894,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"support","depth":15,"bounds":{"left":0.5422208,"top":0.78371906,"width":0.017287234,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.78371906,"width":0.0023271276,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54454786,"top":0.78371906,"width":0.01462766,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":15,"bounds":{"left":0.5422208,"top":0.80606544,"width":0.024601065,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.80606544,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54421544,"top":0.80606544,"width":0.022606382,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":15,"bounds":{"left":0.5422208,"top":0.8284118,"width":0.045212764,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.8284118,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":20,"bounds":{"left":0.54421544,"top":0.8284118,"width":0.04720745,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":15,"bounds":{"left":0.5422208,"top":0.8810854,"width":0.026595745,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.8810854,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54521275,"top":0.8810854,"width":0.023271276,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Stefka Stoyanova","depth":15,"bounds":{"left":0.5422208,"top":0.9034318,"width":0.037898935,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.9034318,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54488033,"top":0.9034318,"width":0.03523936,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Iliyana Netseva","depth":15,"bounds":{"left":0.5422208,"top":0.92577815,"width":0.03324468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.92577815,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":14,"bounds":{"left":0.54355055,"top":0.92577815,"width":0.031914894,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tomov","depth":15,"bounds":{"left":0.5422208,"top":0.9481245,"width":0.030917553,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.9481245,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":11,"bounds":{"left":0.54488033,"top":0.9481245,"width":0.027925532,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Petko Kashinski","depth":15,"bounds":{"left":0.5422208,"top":0.97047085,"width":0.034574468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.97047085,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":14,"bounds":{"left":0.54521275,"top":0.97047085,"width":0.03158245,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":15,"bounds":{"left":0.5422208,"top":0.9928172,"width":0.03523936,"height":0.0031923384},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Todor Stamatov","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.034574468,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.038231384,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Ves","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.007978723,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Mira","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.010305851,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.032912236,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"James Graham","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.031914894,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Lukas Kovalik","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.029587766,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"you","depth":15,"bounds":{"left":0.5744681,"top":0.9952115,"width":0.0063164895,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Jira Cloud","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.021941489,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Toast","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.011635638,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Google Calendar","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.030585106,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Messages","depth":9,"bounds":{"left":0.60206115,"top":0.09177973,"width":0.030917553,"height":0.030327214},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Messages","depth":11,"bounds":{"left":0.61136967,"top":0.10055866,"width":0.018949468,"height":0.012769354},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.61136967,"top":0.10055866,"width":0.0039893617,"height":0.012769354}},{"char_start":1,"char_count":7,"bounds":{"left":0.61535907,"top":0.10055866,"width":0.014960106,"height":0.012769354}}],"role_description":"text"},{"role":"AXRadioButton","text":"Add canvas","depth":10,"bounds":{"left":0.63397604,"top":0.09177973,"width":0.034242023,"height":0.030327214},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Add canvas","depth":12,"bounds":{"left":0.64328456,"top":0.10055866,"width":0.022273935,"height":0.012769354},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.64328456,"top":0.10055866,"width":0.0033244682,"height":0.012769354}},{"char_start":1,"char_count":9,"bounds":{"left":0.6462766,"top":0.10055866,"width":0.019281914,"height":0.012769354}}],"role_description":"text"},{"role":"AXPopUpButton","text":"Add and Edit Channel Tabs","depth":9,"bounds":{"left":0.66921544,"top":0.09177973,"width":0.010970744,"height":0.030327214},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Canvas","depth":9,"bounds":{"left":0.59640956,"top":0.0518755,"width":0.015957447,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"List","depth":9,"bounds":{"left":0.59640956,"top":0.0518755,"width":0.007978723,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":9,"bounds":{"left":0.59640956,"top":0.0518755,"width":0.013962766,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Iliyana Netseva","depth":14,"bounds":{"left":0.6412899,"top":0.12529927,"width":0.041888297,"height":0.021548284},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Iliyana Netseva","depth":16,"bounds":{"left":0.6412899,"top":0.12689546,"width":0.04055851,"height":0.017557861},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.6412899,"top":0.12769353,"width":0.0016622341,"height":0.017557861}},{"char_start":1,"char_count":14,"bounds":{"left":0.64295214,"top":0.12769353,"width":0.038896278,"height":0.017557861}}],"role_description":"text"},{"role":"AXStaticText","text":"This conversation is just between","depth":14,"bounds":{"left":0.60339093,"top":0.1915403,"width":0.08676862,"height":0.016759777},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.60339093,"top":0.1915403,"width":0.0033244682,"height":0.016759777}},{"char_start":1,"char_count":32,"bounds":{"left":0.60671544,"top":0.1915403,"width":0.08211436,"height":0.016759777}}],"role_description":"text"},{"role":"AXLink","text":"@Iliyana Netseva","depth":14,"bounds":{"left":0.69015956,"top":0.19074222,"width":0.045212764,"height":0.018355945},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"@Iliyana Netseva","depth":15,"bounds":{"left":0.69082445,"top":0.1915403,"width":0.043882977,"height":0.016759777},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"and you. Check out their profile to learn more about them.","depth":14,"bounds":{"left":0.73537236,"top":0.1915403,"width":0.1505984,"height":0.016759777},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"View Profile","depth":13,"bounds":{"left":0.60339093,"top":0.22266561,"width":0.035904255,"height":0.02952913},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Jump to date","depth":14,"bounds":{"left":0.7849069,"top":0.2753392,"width":0.025265958,"height":0.023144454},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Lukas Kovalik","depth":15,"bounds":{"left":0.6180186,"top":0.3064645,"width":0.030917553,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":15,"bounds":{"left":0.64893615,"top":0.30806065,"width":0.0026595744,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"Today at 12:32:31 PM","depth":15,"bounds":{"left":0.6515958,"top":0.3104549,"width":0.017287234,"height":0.011971269},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"12:32 PM","depth":16,"bounds":{"left":0.6515958,"top":0.3104549,"width":0.017287234,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"здравей, имаш ли минутка","depth":16,"bounds":{"left":0.6180186,"top":0.3256185,"width":0.061835106,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply in thread","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":17,"on_screen":false,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Iliyana Netseva","depth":15,"bounds":{"left":0.6180186,"top":0.34796488,"width":0.034574468,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":15,"bounds":{"left":0.65226066,"top":0.34956107,"width":0.0026595744,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"Today at 2:51:31 PM","depth":15,"bounds":{"left":0.6549202,"top":0.3519553,"width":0.015292553,"height":0.011971269},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2:51 PM","depth":16,"bounds":{"left":0.6549202,"top":0.3519553,"width":0.015292553,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"okolo 4 chasa svoboden li shte si ?","depth":16,"bounds":{"left":0.6180186,"top":0.36711892,"width":0.07480053,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply in thread","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":17,"on_screen":false,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Lukas Kovalik","depth":15,"bounds":{"left":0.6180186,"top":0.38946527,"width":0.030917553,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":15,"bounds":{"left":0.64893615,"top":0.39106146,"width":0.0026595744,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"Today at 2:52:21 PM","depth":15,"bounds":{"left":0.6515958,"top":0.3934557,"width":0.014960106,"height":0.011971269},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2:52 PM","depth":16,"bounds":{"left":0.6515958,"top":0.3934557,"width":0.014960106,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"здрасти, да но мисля че няма нужда вече","depth":16,"bounds":{"left":0.6180186,"top":0.4086193,"width":0.09541223,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply in thread","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":17,"on_screen":false,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Today at 2:52:47 PM","depth":16,"bounds":{"left":0.60738033,"top":0.4349561,"width":0.007978723,"height":0.011971269},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2:52","depth":17,"bounds":{"left":0.60738033,"top":0.4349561,"width":0.007978723,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"чухме се със Стоян и той беше запознат и по този кейс","depth":16,"bounds":{"left":0.6180186,"top":0.43256184,"width":0.12699468,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8053308250916045488
|
-5892513854291279022
|
idle
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
Stoyan Tomov
Petko Kashinski
Galya Dimitrova
Todor Stamatov
Steliyan Georgiev
Ves
Mira
Nikolay Yankov
James Graham
Lukas Kovalik
you
Jira Cloud
Toast
Google Calendar
Messages
Messages
Add canvas
Add canvas
Add and Edit Channel Tabs
Canvas
List
Folder
Iliyana Netseva
Iliyana Netseva
This conversation is just between
@Iliyana Netseva
@Iliyana Netseva
and you. Check out their profile to learn more about them.
View Profile
Jump to date
Lukas Kovalik
Today at 12:32:31 PM
12:32 PM
здравей, имаш ли минутка
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Iliyana Netseva
Today at 2:51:31 PM
2:51 PM
okolo 4 chasa svoboden li shte si ?
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Lukas Kovalik
Today at 2:52:21 PM
2:52 PM
здрасти, да но мисля че няма нужда вече
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Today at 2:52:47 PM
2:52
чухме се със Стоян и той беше запознат и по този кейс
React with white_check_mark
React with eyes
React with raised_hands
sluckcolVIChRotonWindowHelp= github.com/~ Google Gemini© Pipelines - |Iminnylapp* (JY-20613) Allow owner's role to tAxUemered tines on New codJY-20963 fix deleted object impsJiminny\Exceptions(EmailActivityfrM'inbox (1734) - Aukas.kovallk@jlminoonye Rosd ve php 81.15 dedPlatform Sorint 5.02 - Platform Te8 Jiminny7 (SRD-6881] (On demand) TransSona Strbramaal an at 27/05/2021O Illyana Netsova at 27/05/2026, 18Jy 20910 schedule parallel up: XGemini 3.1 Pro ~That is a very common misconception, but it is not true anymoreWhile it is true that many years ago (around 2014-2016) phpredis had some bugs regarding theorder in which EX and NX arguments were passed, it has fully supported combining them into asingle atomic command for a very long time.To do this atomically using the phpredis driver, you simply pass an associative array of options asthe third argumentHere is exactly how you would write it in your code:PHPSlockKey = sprintf(self::LOCK_KEY, $this->updateTarget);1/ This executes a single, atomic SET conand with both NX and EXSacquired = Redis::set(slockkey, true, L'NX, "EX => seLf::LOCK.TTL-SECONDSJ)8if (! Sacquired) €// Another process already holds the locknetuyn:/1 ... report your metrics …..By relying on separate (get, set, and expire commands, you are leaving a microsecond gapopen. In your new multi-process Supervisor architecture, two parallel workers can check the get,both see (null, and both proceed to write the lock and push duplicate metrics. Using the arraysyntax above closes that gap completely.A Cleaner Laravel AlternativeSince vou are inside a Laravel app, you don't even need to fight with driver-specific Redis syntax.Ask GeminilYour lim nou chate trhn" uend to imnroy our moddle Camioiie 4l1t cao mata mtake co donbls chack " Yoir oracy & CaminSummarize pageI1 OpenJy 20910 schedule paraliCFilter tec.~ Б app~ fi Component/ESEntityTypeReindexer.phpReindexTargetStatsReporter,p...UpdateProcessManager.phpv Consolev # Commands/ElasticsearchAsyncUpdateEsEntities.php•1AsvncUodateSupervisor.ohoDeleteEmailDocumentsCom..ReindexEntityTypes.php•1 RemoveGhostParticipantsCo..ResetAsyncElasticSearchCo...® Kernel.phpv= Contracts/2SStopSignallnterface.php~ Ei Traits2) GracefullvStonoable.ohov # tests/Unit/Component/ESEntityTypeReindexerTest.phpReindexTargetStatsReporterTes…UpdateProcessManagerTest.phpHoeActivityLaterMoneiJiminnY ….inrede@ Threads6 HuddiesDrafts & sent© Directories01Ab External connections# Starred8 platform-backend-….8 platform-inner-team® Channels# ai-chapter# alerts# backend# bugs# confusion-clinic# donut_time# engineering# general# happy_birthday8 infosec_internal_all# infra-changest infrastructure del# ibu-team-info# jiminny-bg# platform-team# platform-tickets# product launches# random# releases# support# thank-yous# the _people_of jimi.…^ Direct messageswasiwashenf. Stefka Stoyanova(C. lliyana Netseva88. Stoyan Tomov&® Petko KashinskiInu co moy tozUiolQ Describe what you are looking for* {L.lliyana Netsevae MessagerNoR0d canvislliyana Netseva•Tnis conversaton is just derween elliyana Neseva and you, cneck oue their pronle to learn more aboud tnem.View Profilewtkas Kovalnk wxy pvздравей, имаш ли минуткаivana Nerseva 2-54PMokolo 4 chasa svoboden ln shte siwuKas Kowalkepiryrчухме се сьс Стоян и той беше запознат и по този кейспол този кейс имам предвил httos://nimiaoustascian.nat/brouce/CO0.6881Jira Cloud +** lue se sel I ia crition in saved search disappearsReady for customera Lukas Kovalik (you)eottodmatyikeyвиж коментари и зко тодова нещо дрчго може дя се чмемwiyana Neseva 7:5o PMpopravi me ako greshaLukas Kovalik 2:57PMvidgh kakvo si napisal, neshtoto koeto mi e stranno e che razlikata mejdu tova koeto pravi ta i tova koeto si napisal ti e natiskaneto na enter-a za da izpulni search-aда се търси по transcriptlliyana Netseva 3:04 PMima li prichina tova da e napraveno po tozi nachin ili prosto taka e napraveno nqkogaukac Kowaik aciorНА ЭНАМ КОМПОЗИТЕН ПИЛТКЯ А МОЖА ВИ Т0968Я ЛАТСИ НАСТООМШІ НеЦІКТИТИ ПОСЛА ЛА НАСЛИВИШ АЛНЯ ЗА9ЯКА А СКАЛТИМ НаПЛИМетука не сьм сигурен, по-скоро е зи поолукаMessage Iliyana Netseva+ Aa...
|
85352
|
NULL
|
NULL
|
NULL
|
|
85353
|
2922
|
4
|
2026-05-28T12:20:39.000751+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970839000_m1.jpg...
|
Slack
|
Iliyana Netseva (DM) - Jiminny Inc - 2 new items - Iliyana Netseva (DM) - Jiminny Inc - 2 new items - Slack...
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
Stoyan Tomov
Petko Kashinski
Galya Dimitrova
Todor Stamatov
Steliyan Georgiev
Ves
Mira
Nikolay Yankov
James Graham
Lukas Kovalik
you
Jira Cloud
Toast
Google Calendar
Messages
Messages
Add canvas
Add canvas
Add and Edit Channel Tabs
Canvas
List
Folder
Iliyana Netseva
Iliyana Netseva
This conversation is just between
@Iliyana Netseva
@Iliyana Netseva
and you. Check out their profile to learn more about them.
View Profile
Jump to date
Lukas Kovalik
Today at 12:32:31 PM
12:32 PM
здравей, имаш ли минутка
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Iliyana Netseva
Today at 2:51:31 PM
2:51 PM
okolo 4 chasa svoboden li shte si ?
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Lukas Kovalik
Today at 2:52:21 PM
2:52 PM
здрасти, да но мисля че няма нужда вече
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Today at 2:52:47 PM
2:52
чухме се със Стоян и той беше запознат и по този кейс
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Today at 2:53:22 PM
2:53
под този кейс имам предвид
https://jiminny.atlassian.net/browse/SRD-6881
https://jiminny.atlassian.net/browse/SRD-6881
Jira Cloud
Jira Cloud
Remove preview
Jira Cloud Bug SRD-6881 [On demand] Transcription in saved search disappears Bug SRD-6881 in Jira Cloud Preview in Slack Status Ready for customer Assignee Lukas Kovalik(you) As of today at 2:53 PM Refresh Open in Jira ✨ Summarize
[On demand] Transcription in saved search disappears
Bug SRD-6881 in Jira Cloud
Preview in Slack
Status
Ready for customer
Assignee
Lukas Kovalik
(you)
As of today at 2:53 PM
Refresh
Open in Jira
✨ Summarize
Open in browser...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":13,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":13,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":13,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":13,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":13,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":13,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"platform-backend-engineers","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"bugs","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"donut_time","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"general","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"happy_birthday","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"infosec_internal_all","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"infra-changes","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"infrastructure_dev","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"jbu-team-info","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"platform-team","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"random","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"support","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Stefka Stoyanova","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Iliyana Netseva","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tomov","depth":15,"bounds":{"left":0.56805557,"top":0.0,"width":0.06458333,"height":0.02},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Petko Kashinski","depth":15,"bounds":{"left":0.56805557,"top":0.0,"width":0.072222225,"height":0.02},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":15,"bounds":{"left":0.56805557,"top":0.0,"width":0.07361111,"height":0.0044444446},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Todor Stamatov","depth":15,"bounds":{"left":0.56805557,"top":0.0,"width":0.072222225,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":15,"bounds":{"left":0.56805557,"top":0.0,"width":0.07986111,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Ves","depth":15,"bounds":{"left":0.56805557,"top":0.0,"width":0.016666668,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Mira","depth":15,"bounds":{"left":0.56805557,"top":0.0,"width":0.021527778,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":15,"bounds":{"left":0.56805557,"top":0.0,"width":0.06875,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"James Graham","depth":15,"bounds":{"left":0.56805557,"top":0.0,"width":0.06666667,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Lukas Kovalik","depth":15,"bounds":{"left":0.56805557,"top":0.0,"width":0.061805554,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"you","depth":15,"bounds":{"left":0.6354167,"top":0.0,"width":0.013194445,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Jira Cloud","depth":15,"bounds":{"left":0.56805557,"top":0.0,"width":0.045833334,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Toast","depth":15,"bounds":{"left":0.56805557,"top":0.0,"width":0.024305556,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Google Calendar","depth":15,"bounds":{"left":0.56805557,"top":0.0,"width":0.06388889,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Messages","depth":9,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Messages","depth":11,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Add canvas","depth":10,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Add canvas","depth":12,"on_screen":true,"role_description":"text"},{"role":"AXPopUpButton","text":"Add and Edit Channel Tabs","depth":9,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Canvas","depth":9,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"List","depth":9,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":9,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Iliyana Netseva","depth":14,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Iliyana Netseva","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"This conversation is just between","depth":14,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"@Iliyana Netseva","depth":14,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"@Iliyana Netseva","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"and you. Check out their profile to learn more about them.","depth":14,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"View Profile","depth":13,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Jump to date","depth":14,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Lukas Kovalik","depth":15,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"Today at 12:32:31 PM","depth":15,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"12:32 PM","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"здравей, имаш ли минутка","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply in thread","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":17,"on_screen":false,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Iliyana Netseva","depth":15,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"Today at 2:51:31 PM","depth":15,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2:51 PM","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"okolo 4 chasa svoboden li shte si ?","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply in thread","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":17,"on_screen":false,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Lukas Kovalik","depth":15,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"Today at 2:52:21 PM","depth":15,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2:52 PM","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"здрасти, да но мисля че няма нужда вече","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply in thread","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":17,"on_screen":false,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Today at 2:52:47 PM","depth":16,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2:52","depth":17,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"чухме се със Стоян и той беше запознат и по този кейс","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply in thread","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":17,"on_screen":false,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Today at 2:53:22 PM","depth":16,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2:53","depth":17,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"под този кейс имам предвид","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"https://jiminny.atlassian.net/browse/SRD-6881","depth":16,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"https://jiminny.atlassian.net/browse/SRD-6881","depth":17,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Jira Cloud","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Jira Cloud","depth":15,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Remove preview","depth":17,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Jira Cloud Bug SRD-6881 [On demand] Transcription in saved search disappears Bug SRD-6881 in Jira Cloud Preview in Slack Status Ready for customer Assignee Lukas Kovalik(you) As of today at 2:53 PM Refresh Open in Jira ✨ Summarize","depth":17,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"[On demand] Transcription in saved search disappears","depth":18,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Bug SRD-6881 in Jira Cloud","depth":19,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Preview in Slack","depth":19,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Status","depth":18,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Ready for customer","depth":18,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Assignee","depth":18,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Lukas Kovalik","depth":19,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"(you)","depth":19,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"As of today at 2:53 PM","depth":19,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Refresh","depth":19,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open in Jira","depth":19,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"✨ Summarize","depth":19,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Open in browser","depth":18,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
7635596047977670400
|
-1285331609672318606
|
idle
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
Stoyan Tomov
Petko Kashinski
Galya Dimitrova
Todor Stamatov
Steliyan Georgiev
Ves
Mira
Nikolay Yankov
James Graham
Lukas Kovalik
you
Jira Cloud
Toast
Google Calendar
Messages
Messages
Add canvas
Add canvas
Add and Edit Channel Tabs
Canvas
List
Folder
Iliyana Netseva
Iliyana Netseva
This conversation is just between
@Iliyana Netseva
@Iliyana Netseva
and you. Check out their profile to learn more about them.
View Profile
Jump to date
Lukas Kovalik
Today at 12:32:31 PM
12:32 PM
здравей, имаш ли минутка
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Iliyana Netseva
Today at 2:51:31 PM
2:51 PM
okolo 4 chasa svoboden li shte si ?
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Lukas Kovalik
Today at 2:52:21 PM
2:52 PM
здрасти, да но мисля че няма нужда вече
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Today at 2:52:47 PM
2:52
чухме се със Стоян и той беше запознат и по този кейс
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Today at 2:53:22 PM
2:53
под този кейс имам предвид
https://jiminny.atlassian.net/browse/SRD-6881
https://jiminny.atlassian.net/browse/SRD-6881
Jira Cloud
Jira Cloud
Remove preview
Jira Cloud Bug SRD-6881 [On demand] Transcription in saved search disappears Bug SRD-6881 in Jira Cloud Preview in Slack Status Ready for customer Assignee Lukas Kovalik(you) As of today at 2:53 PM Refresh Open in Jira ✨ Summarize
[On demand] Transcription in saved search disappears
Bug SRD-6881 in Jira Cloud
Preview in Slack
Status
Ready for customer
Assignee
Lukas Kovalik
(you)
As of today at 2:53 PM
Refresh
Open in Jira
✨ Summarize
Open in browser
PhpStormFileEditViewNavigateCodeRefactorRun-zshToolsGitWindowHelpec2-user@ip-10-30-129-190:~screenpipe"O 84‹$0(ah]БГ100% <78• Thu 28 May 15:20:38181ec2-user@ip-10-30-140-…$7DOCKER#_####_#####\\###||\#/V~'881DEV (-zsh)[EMAIL] Linux 2023 (ECS Optimized)_/m/For documentation, visit [URL_WITH_CREDENTIALS] ~]$ exitlogoutConnection to jiminny-prod-ecsi closed.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ applukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ vprodWarning: Permanently added 'jiminny-prod-ecs1' (ED25519) to the list of known hosts.A newer release of "Amazon Linux" is available.Version 2023.10.20260330:Version 2023.11.20260406:Version 2023.11.20260413:Version2023.11.20260427:Version2023.11.20260505:Version 2023.11.20260509:Version 2023.11.20260511:Version 2023.11.20260514:Run "/usr/bin/dnf check-release-update" for full release and version update info#_~\ ####_\ #####\\###1\#/Amazon Linux 2023 (ECS Optimized)/m/'For documentation, visit [URL_WITH_CREDENTIALS] ~]$ client_loop: send disconnect: Broken pipeukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-relay) $I...
|
85350
|
NULL
|
NULL
|
NULL
|
|
85352
|
2923
|
6
|
2026-05-28T12:20:20.719184+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970820719_m2.jpg...
|
Slack
|
Iliyana Netseva (DM) - Jiminny Inc - 2 new items - Iliyana Netseva (DM) - Jiminny Inc - 2 new items - Slack...
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
Stoyan Tomov
Petko Kashinski
Galya Dimitrova
Todor Stamatov
Steliyan Georgiev
Ves
Mira
Nikolay Yankov
James Graham
Lukas Kovalik
you
Jira Cloud
Toast
Google Calendar
Messages
Messages
Add canvas
Add canvas
Add and Edit Channel Tabs
Canvas
List
Folder
Iliyana Netseva
Iliyana Netseva
This conversation is just between
@Iliyana Netseva
@Iliyana Netseva
and you. Check out their profile to learn more about them.
View Profile
Jump to date
Lukas Kovalik
Today at 12:32:31 PM
12:32 PM
здравей, имаш ли минутка
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Iliyana Netseva
Today at 2:51:31 PM
2:51 PM
okolo 4 chasa svoboden li shte si ?
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Lukas Kovalik
Today at 2:52:21 PM
2:52 PM
здрасти, да но мисля че няма нужда вече
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Today at 2:52:47 PM
2:52
чухме се със Стоян и той беше запознат и по този кейс
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Today at 2:53:22 PM
2:53
под този кейс имам предвид...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"bounds":{"left":0.5056516,"top":0.058260176,"width":0.011968086,"height":0.028731046},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"bounds":{"left":0.50299203,"top":0.10055866,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":8,"bounds":{"left":0.50664896,"top":0.13806863,"width":0.009973404,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":6,"bounds":{"left":0.50299203,"top":0.15482841,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":8,"bounds":{"left":0.50764626,"top":0.19233839,"width":0.007978723,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":6,"bounds":{"left":0.50299203,"top":0.20909816,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":8,"bounds":{"left":0.5053192,"top":0.24660814,"width":0.012965426,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5053192,"top":0.24660814,"width":0.0026595744,"height":0.011173184}},{"char_start":1,"char_count":7,"bounds":{"left":0.50764626,"top":0.24660814,"width":0.010638298,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":6,"bounds":{"left":0.50299203,"top":0.26336792,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":8,"bounds":{"left":0.50797874,"top":0.3008779,"width":0.0076462766,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50797874,"top":0.3008779,"width":0.0019946808,"height":0.011173184}},{"char_start":1,"char_count":4,"bounds":{"left":0.5099734,"top":0.3008779,"width":0.0056515955,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":6,"bounds":{"left":0.50299203,"top":0.31763768,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":8,"bounds":{"left":0.50731385,"top":0.35514766,"width":0.008643617,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50731385,"top":0.35514766,"width":0.0019946808,"height":0.011173184}},{"char_start":1,"char_count":4,"bounds":{"left":0.5093085,"top":0.35514766,"width":0.0066489363,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":6,"bounds":{"left":0.50299203,"top":0.3719074,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":8,"bounds":{"left":0.50731385,"top":0.4094174,"width":0.008976064,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50731385,"top":0.4094174,"width":0.0033244682,"height":0.011173184}},{"char_start":1,"char_count":3,"bounds":{"left":0.5106383,"top":0.4094174,"width":0.0056515955,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":13,"bounds":{"left":0.5369016,"top":0.10055866,"width":0.018949468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5369016,"top":0.10055866,"width":0.003656915,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.5405585,"top":0.10055866,"width":0.015292553,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":13,"bounds":{"left":0.5369016,"top":0.12290503,"width":0.01761968,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5369016,"top":0.12290503,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.5398936,"top":0.12290503,"width":0.01462766,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":13,"bounds":{"left":0.5369016,"top":0.1452514,"width":0.018284574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5369016,"top":0.1452514,"width":0.0039893617,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54089093,"top":0.1452514,"width":0.014295213,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":13,"bounds":{"left":0.5369016,"top":0.16759777,"width":0.02925532,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5369016,"top":0.16759777,"width":0.003656915,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.5405585,"top":0.16759777,"width":0.025265958,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"1","depth":13,"bounds":{"left":0.5884308,"top":0.16839585,"width":0.0026595744,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":13,"bounds":{"left":0.5369016,"top":0.18994413,"width":0.024268618,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5369016,"top":0.18994413,"width":0.0039893617,"height":0.014365523}},{"char_start":1,"char_count":10,"bounds":{"left":0.54089093,"top":0.18994413,"width":0.020279255,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"platform-backend-engineers","depth":15,"bounds":{"left":0.5422208,"top":0.28411812,"width":0.044215426,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.28411812,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":25,"bounds":{"left":0.54521275,"top":0.28411812,"width":0.058843084,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":15,"bounds":{"left":0.5422208,"top":0.3064645,"width":0.04454787,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.3064645,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":18,"bounds":{"left":0.54521275,"top":0.3064645,"width":0.04155585,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":15,"bounds":{"left":0.5422208,"top":0.35913807,"width":0.022273935,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.35913807,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54488033,"top":0.35913807,"width":0.019614361,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":15,"bounds":{"left":0.5422208,"top":0.38148445,"width":0.012300532,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.38148445,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":5,"bounds":{"left":0.54488033,"top":0.38148445,"width":0.00930851,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":15,"bounds":{"left":0.5422208,"top":0.4038308,"width":0.018284574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.4038308,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54521275,"top":0.4038308,"width":0.015292553,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"bugs","depth":15,"bounds":{"left":0.5422208,"top":0.42617717,"width":0.010638298,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.42617717,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":3,"bounds":{"left":0.54521275,"top":0.42617717,"width":0.00731383,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":15,"bounds":{"left":0.5422208,"top":0.44852355,"width":0.034574468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.44852355,"width":0.0023271276,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54454786,"top":0.44852355,"width":0.032247342,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"donut_time","depth":15,"bounds":{"left":0.5422208,"top":0.4708699,"width":0.025265958,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.4708699,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54521275,"top":0.4708699,"width":0.022273935,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":15,"bounds":{"left":0.5422208,"top":0.49321628,"width":0.025930852,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.49321628,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":10,"bounds":{"left":0.54488033,"top":0.49321628,"width":0.022938829,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"general","depth":15,"bounds":{"left":0.5422208,"top":0.51556265,"width":0.016289894,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.51556265,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54488033,"top":0.51556265,"width":0.013297873,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"happy_birthday","depth":15,"bounds":{"left":0.5422208,"top":0.53790903,"width":0.033909574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.53790903,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":13,"bounds":{"left":0.54521275,"top":0.53790903,"width":0.030917553,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"infosec_internal_all","depth":15,"bounds":{"left":0.5422208,"top":0.5602554,"width":0.041888297,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.5602554,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":19,"bounds":{"left":0.54355055,"top":0.5602554,"width":0.040226065,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"infra-changes","depth":15,"bounds":{"left":0.5422208,"top":0.5826017,"width":0.029587766,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.5826017,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54355055,"top":0.5826017,"width":0.02825798,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"infrastructure_dev","depth":15,"bounds":{"left":0.5422208,"top":0.6049481,"width":0.040226065,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.6049481,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":17,"bounds":{"left":0.54355055,"top":0.6049481,"width":0.038896278,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"jbu-team-info","depth":15,"bounds":{"left":0.5422208,"top":0.6272945,"width":0.029920213,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.6272945,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54355055,"top":0.6272945,"width":0.028590426,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":15,"bounds":{"left":0.5422208,"top":0.64964086,"width":0.023271276,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.64964086,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54355055,"top":0.64964086,"width":0.021609042,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"platform-team","depth":15,"bounds":{"left":0.5422208,"top":0.67198724,"width":0.03158245,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.67198724,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54521275,"top":0.67198724,"width":0.028590426,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":15,"bounds":{"left":0.5422208,"top":0.6943336,"width":0.034906916,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.6943336,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54521275,"top":0.6943336,"width":0.031914894,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":15,"bounds":{"left":0.5422208,"top":0.71668,"width":0.03856383,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.71668,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54521275,"top":0.71668,"width":0.03557181,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"random","depth":15,"bounds":{"left":0.5422208,"top":0.7390263,"width":0.016954787,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.7390263,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":5,"bounds":{"left":0.54421544,"top":0.7390263,"width":0.014960106,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":15,"bounds":{"left":0.5422208,"top":0.7613727,"width":0.018284574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.7613727,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":7,"bounds":{"left":0.54421544,"top":0.7613727,"width":0.016289894,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"support","depth":15,"bounds":{"left":0.5422208,"top":0.78371906,"width":0.017287234,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.78371906,"width":0.0023271276,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54454786,"top":0.78371906,"width":0.01462766,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":15,"bounds":{"left":0.5422208,"top":0.80606544,"width":0.024601065,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.80606544,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54421544,"top":0.80606544,"width":0.022606382,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":15,"bounds":{"left":0.5422208,"top":0.8284118,"width":0.045212764,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.8284118,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":20,"bounds":{"left":0.54421544,"top":0.8284118,"width":0.04720745,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":15,"bounds":{"left":0.5422208,"top":0.8810854,"width":0.026595745,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.8810854,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54521275,"top":0.8810854,"width":0.023271276,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Stefka Stoyanova","depth":15,"bounds":{"left":0.5422208,"top":0.9034318,"width":0.037898935,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.9034318,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54488033,"top":0.9034318,"width":0.03523936,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Iliyana Netseva","depth":15,"bounds":{"left":0.5422208,"top":0.92577815,"width":0.03324468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.92577815,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":14,"bounds":{"left":0.54355055,"top":0.92577815,"width":0.031914894,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tomov","depth":15,"bounds":{"left":0.5422208,"top":0.9481245,"width":0.030917553,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.9481245,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":11,"bounds":{"left":0.54488033,"top":0.9481245,"width":0.027925532,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Petko Kashinski","depth":15,"bounds":{"left":0.5422208,"top":0.97047085,"width":0.034574468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.97047085,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":14,"bounds":{"left":0.54521275,"top":0.97047085,"width":0.03158245,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":15,"bounds":{"left":0.5422208,"top":0.9928172,"width":0.03523936,"height":0.0031923384},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Todor Stamatov","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.034574468,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.038231384,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Ves","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.007978723,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Mira","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.010305851,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.032912236,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"James Graham","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.031914894,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Lukas Kovalik","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.029587766,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"you","depth":15,"bounds":{"left":0.5744681,"top":0.9952115,"width":0.0063164895,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Jira Cloud","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.021941489,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Toast","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.011635638,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Google Calendar","depth":15,"bounds":{"left":0.5422208,"top":0.9952115,"width":0.030585106,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Messages","depth":9,"bounds":{"left":0.60206115,"top":0.09177973,"width":0.030917553,"height":0.030327214},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Messages","depth":11,"bounds":{"left":0.61136967,"top":0.10055866,"width":0.018949468,"height":0.012769354},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.61136967,"top":0.10055866,"width":0.0039893617,"height":0.012769354}},{"char_start":1,"char_count":7,"bounds":{"left":0.61535907,"top":0.10055866,"width":0.014960106,"height":0.012769354}}],"role_description":"text"},{"role":"AXRadioButton","text":"Add canvas","depth":10,"bounds":{"left":0.63397604,"top":0.09177973,"width":0.034242023,"height":0.030327214},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Add canvas","depth":12,"bounds":{"left":0.64328456,"top":0.10055866,"width":0.022273935,"height":0.012769354},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.64328456,"top":0.10055866,"width":0.0033244682,"height":0.012769354}},{"char_start":1,"char_count":9,"bounds":{"left":0.6462766,"top":0.10055866,"width":0.019281914,"height":0.012769354}}],"role_description":"text"},{"role":"AXPopUpButton","text":"Add and Edit Channel Tabs","depth":9,"bounds":{"left":0.66921544,"top":0.09177973,"width":0.010970744,"height":0.030327214},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Canvas","depth":9,"bounds":{"left":0.59640956,"top":0.0518755,"width":0.015957447,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"List","depth":9,"bounds":{"left":0.59640956,"top":0.0518755,"width":0.007978723,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":9,"bounds":{"left":0.59640956,"top":0.0518755,"width":0.013962766,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Iliyana Netseva","depth":14,"bounds":{"left":0.6412899,"top":0.12529927,"width":0.041888297,"height":0.021548284},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Iliyana Netseva","depth":16,"bounds":{"left":0.6412899,"top":0.12689546,"width":0.04055851,"height":0.017557861},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.6412899,"top":0.12769353,"width":0.0016622341,"height":0.017557861}},{"char_start":1,"char_count":14,"bounds":{"left":0.64295214,"top":0.12769353,"width":0.038896278,"height":0.017557861}}],"role_description":"text"},{"role":"AXStaticText","text":"This conversation is just between","depth":14,"bounds":{"left":0.60339093,"top":0.1915403,"width":0.08676862,"height":0.016759777},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.60339093,"top":0.1915403,"width":0.0033244682,"height":0.016759777}},{"char_start":1,"char_count":32,"bounds":{"left":0.60671544,"top":0.1915403,"width":0.08211436,"height":0.016759777}}],"role_description":"text"},{"role":"AXLink","text":"@Iliyana Netseva","depth":14,"bounds":{"left":0.69015956,"top":0.19074222,"width":0.045212764,"height":0.018355945},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"@Iliyana Netseva","depth":15,"bounds":{"left":0.69082445,"top":0.1915403,"width":0.043882977,"height":0.016759777},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"and you. Check out their profile to learn more about them.","depth":14,"bounds":{"left":0.73537236,"top":0.1915403,"width":0.1505984,"height":0.016759777},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"View Profile","depth":13,"bounds":{"left":0.60339093,"top":0.22266561,"width":0.035904255,"height":0.02952913},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Jump to date","depth":14,"bounds":{"left":0.7849069,"top":0.2753392,"width":0.025265958,"height":0.023144454},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Lukas Kovalik","depth":15,"bounds":{"left":0.6180186,"top":0.3064645,"width":0.030917553,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":15,"bounds":{"left":0.64893615,"top":0.30806065,"width":0.0026595744,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"Today at 12:32:31 PM","depth":15,"bounds":{"left":0.6515958,"top":0.3104549,"width":0.017287234,"height":0.011971269},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"12:32 PM","depth":16,"bounds":{"left":0.6515958,"top":0.3104549,"width":0.017287234,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"здравей, имаш ли минутка","depth":16,"bounds":{"left":0.6180186,"top":0.3256185,"width":0.061835106,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply in thread","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":17,"on_screen":false,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Iliyana Netseva","depth":15,"bounds":{"left":0.6180186,"top":0.34796488,"width":0.034574468,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":15,"bounds":{"left":0.65226066,"top":0.34956107,"width":0.0026595744,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"Today at 2:51:31 PM","depth":15,"bounds":{"left":0.6549202,"top":0.3519553,"width":0.015292553,"height":0.011971269},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2:51 PM","depth":16,"bounds":{"left":0.6549202,"top":0.3519553,"width":0.015292553,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"okolo 4 chasa svoboden li shte si ?","depth":16,"bounds":{"left":0.6180186,"top":0.36711892,"width":0.07480053,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply in thread","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":17,"on_screen":false,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Lukas Kovalik","depth":15,"bounds":{"left":0.6180186,"top":0.38946527,"width":0.030917553,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":15,"bounds":{"left":0.64893615,"top":0.39106146,"width":0.0026595744,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"Today at 2:52:21 PM","depth":15,"bounds":{"left":0.6515958,"top":0.3934557,"width":0.014960106,"height":0.011971269},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2:52 PM","depth":16,"bounds":{"left":0.6515958,"top":0.3934557,"width":0.014960106,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"здрасти, да но мисля че няма нужда вече","depth":16,"bounds":{"left":0.6180186,"top":0.4086193,"width":0.09541223,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply in thread","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":17,"on_screen":false,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Today at 2:52:47 PM","depth":16,"bounds":{"left":0.60738033,"top":0.4349561,"width":0.007978723,"height":0.011971269},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2:52","depth":17,"bounds":{"left":0.60738033,"top":0.4349561,"width":0.007978723,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"чухме се със Стоян и той беше запознат и по този кейс","depth":16,"bounds":{"left":0.6180186,"top":0.43256184,"width":0.12699468,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply in thread","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":17,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":17,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":17,"on_screen":false,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Today at 2:53:22 PM","depth":16,"bounds":{"left":0.60738033,"top":0.45889863,"width":0.007978723,"height":0.011971269},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2:53","depth":17,"bounds":{"left":0.60738033,"top":0.45889863,"width":0.007978723,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"под този кейс имам предвид","depth":16,"bounds":{"left":0.6180186,"top":0.45650437,"width":0.06781915,"height":0.015163607},"on_screen":true,"role_description":"text"}]...
|
8522312269222833002
|
-1280264885910470574
|
visual_change
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
Stoyan Tomov
Petko Kashinski
Galya Dimitrova
Todor Stamatov
Steliyan Georgiev
Ves
Mira
Nikolay Yankov
James Graham
Lukas Kovalik
you
Jira Cloud
Toast
Google Calendar
Messages
Messages
Add canvas
Add canvas
Add and Edit Channel Tabs
Canvas
List
Folder
Iliyana Netseva
Iliyana Netseva
This conversation is just between
@Iliyana Netseva
@Iliyana Netseva
and you. Check out their profile to learn more about them.
View Profile
Jump to date
Lukas Kovalik
Today at 12:32:31 PM
12:32 PM
здравей, имаш ли минутка
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Iliyana Netseva
Today at 2:51:31 PM
2:51 PM
okolo 4 chasa svoboden li shte si ?
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Lukas Kovalik
Today at 2:52:21 PM
2:52 PM
здрасти, да но мисля че няма нужда вече
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Today at 2:52:47 PM
2:52
чухме се със Стоян и той беше запознат и по този кейс
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Today at 2:53:22 PM
2:53
под този кейс имам предвид
sluckcolVIChRotonWindowHelp= github.com/~ Google Gemini© Pipelines - |Iminnylapp* (JY-20613) Allow owner's role to tAxUemered tines on New codJY-20963 fix deleted object impsJiminny\Exceptions(EmailActivityfrM'inbox (1734) - Aukas.kovallk@jlminoonye Rosd ve php 81.15 dedPlatform Sorint 5.02 - Platform Te8 Jiminny7 (SRD-6881] (On demand) TransSona Strbramaal an at 27/05/2021O Illyana Netsova at 27/05/2026, 18Jy 20910 schedule parallel up: XGemini 3.1 Pro ~That is a very common misconception, but it is not true anymoreWhile it is true that many years ago (around 2014-2016) phpredis had some bugs regarding theorder in which EX and NX arguments were passed, it has fully supported combining them into asingle atomic command for a very long time.To do this atomically using the phpredis driver, you simply pass an associative array of options asthe third argumentHere is exactly how you would write it in your code:PHPSlockKey = sprintf(self::LOCK_KEY, $this->updateTarget);1/ This executes a single, atomic SET conand with both NX and EXSacquired = Redis::set(slockkey, true, L'NX, "EX => seLf::LOCK.TTL-SECONDSJ)8if (! Sacquired) €// Another process already holds the locknetuyn:/1 ... report your metrics …..By relying on separate (get, set, and expire commands, you are leaving a microsecond gapopen. In your new multi-process Supervisor architecture, two parallel workers can check the get,both see (null, and both proceed to write the lock and push duplicate metrics. Using the arraysyntax above closes that gap completely.A Cleaner Laravel AlternativeSince vou are inside a Laravel app, you don't even need to fight with driver-specific Redis syntax.Ask GeminilYour lim nou chate trhn" uend to imnroy our moddle Camioiie 4l1t cao mata mtake co donbls chack " Yoir oracy & CaminSummarize pageI1 OpenJy 20910 schedule paraliCFilter tec.~ Б app~ fi Component/ESEntityTypeReindexer.phpReindexTargetStatsReporter,p...UpdateProcessManager.phpv Consolev # Commands/ElasticsearchAsyncUpdateEsEntities.php•1AsvncUodateSupervisor.ohoDeleteEmailDocumentsCom..ReindexEntityTypes.php•1 RemoveGhostParticipantsCo..ResetAsyncElasticSearchCo...® Kernel.phpv= Contracts/2SStopSignallnterface.php~ Ei Traits2) GracefullvStonoable.ohov # tests/Unit/Component/ESEntityTypeReindexerTest.phpReindexTargetStatsReporterTes…UpdateProcessManagerTest.phpHoeActivityLaterMoneiJiminnY ….inrede@ Threads6 HuddiesDrafts & sent© Directories01Ab External connections# Starred8 platform-backend-….8 platform-inner-team® Channels# ai-chapter# alerts# backend# bugs# confusion-clinic# donut_time# engineering# general# happy_birthday8 infosec_internal_all# infra-changest infrastructure del# ibu-team-info# jiminny-bg# platform-team# platform-tickets# product launches# random# releases# support# thank-yous# the _people_of jimi.…^ Direct messageswasiwashenf. Stefka Stoyanova(C. lliyana Netseva88. Stoyan Tomov&® Petko KashinskiInu co moy tozUilQ Describe what you are looking for* {L.lliyana Netsevae MessagerNoR0d canvislliyana Netseva•Tnis conversaton is just derween elliyana Neseva and you, cneck oue their pronle to learn more aboud tnem.View Profilewtkas Kovalnk wxy pvздравей, имаш ли минуткаivana Nerseva 2-54PMokolo 4 chasa svoboden ln shte siwuKas Kowalkepiryrчухме се сьс Стоян и той беше запознат и по този кейспол този кейс имам предвил httos://nimiaoustascian.nat/brouce/CO0.6881Jira Cloud +** lue se sel I ia crition in saved search disappearsReady for customera Lukas Kovalik (you)eottodmatyikeyвиж коментари и зко тодова нещо дрчго може дя се чмемwiyana Neseva 7:5o PMpopravi me ako greshaLukas Kovalik 2:57PMvidgh kakvo si napisal, neshtoto koeto mi e stranno e che razlikata mejdu tova koeto pravi ta i tova koeto si napisal ti e natiskaneto na enter-a za da izpulni search-aда се търси по transcriptlliyana Netseva 3:04 PMima li prichina tova da e napraveno po tozi nachin ili prosto taka e napraveno nqkogaukac Kowaik aciorНА ЭНАМ КОМПОЗИТЕН ПИЛТКЯ А МОЖА ВИ Т0968Я ЛАТСИ НАСТООМШІ НеЦІКТИТИ ПОСЛА ЛА НАСЛИВИШ АЛНЯ ЗА9ЯКА А СКАЛТИМ НаПЛИМетука не сьм сигурен, по-скоро е зи поолукаMessage Iliyana Netseva+ Aa...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85351
|
2923
|
5
|
2026-05-28T12:20:17.594072+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970817594_m2.jpg...
|
Firefox
|
Jy 20910 schedule parallel update target processin Jy 20910 schedule parallel update target processing by Vasil-Jiminny · Pull Request #12130 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12130/changes
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0018284575,"top":0.0518755,"width":0.038065158,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
2851836541743944314
|
7224978872138212579
|
visual_change
|
hybrid
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
rircroxViewhsttonooononsrnroiuesTool:WindowHelp• =github.com/v Gooc e GeminiPipelines - jiminny/app(JY-20613) Allow owner's role to bAxUemered tines on New codJY-20963 fix deleted object impJiminny\Exceptions(EmailActivityfrInbox (1,734) - lukas.kovalik Sjlmirnoonre Rose ve php 81.15 dedPlatform Sorint 5.02 - Platform Te8 Jiminny7 (SRD-6881] (On demand) TransSona Strbramaal an at 27/05/2021aIliyana Netseva at 27/05/2026, 1€) Jy 20910 schedule parallel upc XGemini 3.1 Pro ~That is a very common misconception, but it is not true anymoreWhile it is true that many vears ago around 2014-2016 phpredis had some buas regarding theorder in which EX and NX arguments were passed, it has fully supported combining them into asingle atomic command tor a very long timeTo do this atomically usina the phpredis driver, vou simply pass an associative array of options asthe third argumentHere is exactly how you would write it in your code:PHPSlockKey = sprintf(self::LOCK_KEY, Sthis->updateTarget):/l This executes a single, atomic sat coand with both NX and e)Sacquired = Redis::set(slockkey, true, L'NX, "EX => seLf::LOCK.TTL-SECONDSJ)8if (! Sacquired) &// Another process already holds the lockxeturn.I... report your metrics ...By relying on separate get, set, and expire commands, you are leaving a microsecond gapopen. In your new multi-process Supervisor architecture, two parallel workers can check the get .both see null, and both proceed to write the lock and oush duplicate metrics. Usina the arraysyntax above closes that aao completely.A Cleaner laravel AlternativeSince vout are inside a laravel ann vou don't even nopd to ficht with driver-chorifir Redic suntayelcomintYour Jiminny chats aren't used to improve our modeis. Gemini is Al. It can make mistakes, so double check it. Your privacy & GeminiSummarize pagei1 OpenJy 20910 schedule parallel update target processing #12130All commits - Vasil-Jiminny wants to merge 65 commits into master from JY-20910-schedule-parallel-update-target-processing ™v app/Component/ES/ReindexTargetStatsReporter.php• Filter hiles.• E app|v = Component/ESEntityTypeReindexer.phpReindexTargetStatsReporter.p3) UodateProcessManager.php~ #i Consolev # Commands/Elasticsearch• AsyncUpdateEsEntities.phpD AsyncUpdateSupervisor.php- DeleteEmailDocumentsCom…•)ReindexEntityTypes.php•1 RemoveGhostParticipantsCo.….8 ResetAsyncElasticSearchCo..* Kernel. php~ # Contracts/ESStopSignalinterface.php~ Ei Traits2) GracefullvStonoable.ohov E tests/Unit/Component/ES•) EntityTvpeReindexerTest.php•) ReindexTaroetStatsReporterTes…8) UpdateProcessManagerTest.php28#÷997 7 *V itighight All l Match Case ]Match Diacrisies Whole Words 1 of 1 match Reached top of page, continued from bottomGEOMОБ( 5/15 viewedAnaiuns approv• class ReindextargetStatsReportenptlvete const ste io .oT, Se mios - roces saneger-lock ;:peshace zessing senvronment = meiti:ouotcunc.consercnvtronsencscrtnosenvtronsencrvohsthis-senvsronment " senvironmentpublic function setUpdateTarget(string SupdateTarget): voidSthis->updateTarget = SupdateTarget:public function report(int SprocessedDocumentsCount): voidsthis->assertConfigured():Datadog:: increment(stats: 'jiminny.es-documents-synced',sanpleRate: 1.value: SprocessedDocumentsCount,SlockKey = sprintf(self::L0CK_KEY, Sthis-oupdateTaroet):1t/1 1snul1(Redisa.aet(SlockKew)))return;Redis::set(Slockkey, true);Redis::expire(SlockKey. self::L0CK_TTL_SECONDS):Sthis->reportMenoryUsage():private function assertConfiqured(): voidif (Sthis->updateTarget === null) ‹100% 142-• Thu 28 May 15:20:17nit reviewO ∞) 0+95 sssse ~ Viewed...
|
85349
|
NULL
|
NULL
|
NULL
|
|
85350
|
2922
|
3
|
2026-05-28T12:20:06.140519+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970806140_m1.jpg...
|
Slack
|
Vasil Vasilev (DM) - Jiminny Inc - 2 new items - S Vasil Vasilev (DM) - Jiminny Inc - 2 new items - Slack...
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":8,"on_screen":true,"role_description":"text"}]...
|
-8110235827886994960
|
-8111871699717769025
|
click
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
PhpStormFileEditViewNavigateCodeRefactorRunToolsGitWindowHelpec2-user@ip-10-30-129-190:~screenpipe"€ 884‹$0(ah]БГ100% <78 • Thu 28 May 15:20:05181ec2-user@ip-10-30-140-...₴7DOCKER#_####_#####\\###||\#/V~'881DEV (-zsh)O [EMAIL] Linux 2023 (ECS Optimized)_/m/For documentation, visit [URL_WITH_CREDENTIALS] ~]$ exitlogoutConnection to jiminny-prod-ecsi closed.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ applukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ vprodWarning: Permanently added 'jiminny-prod-ecs1' (ED25519) to the list of known hosts.A newer release of "Amazon Linux" is available.Version 2023.10.20260330:Version 2023.11.20260406:Version 2023.11.20260413:Version2023.11.20260427:Version2023.11.20260505:Version 2023.11.20260509:Version 2023.11.20260511:Version 2023.11.20260514:Run "/usr/bin/dnf check-release-update" for full release and version update info#_~\ ####_\ #####\\###1\#/Amazon Linux 2023 (ECS Optimized)/m/'For documentation, visit [URL_WITH_CREDENTIALS] ~]$ client_loop: send disconnect: Broken pipeukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-relay) $I...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85349
|
2923
|
4
|
2026-05-28T12:20:05.298486+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970805298_m2.jpg...
|
Slack
|
Vasil Vasilev (DM) - Jiminny Inc - 2 new items - S Vasil Vasilev (DM) - Jiminny Inc - 2 new items - Slack...
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"bounds":{"left":0.5056516,"top":0.058260176,"width":0.011968086,"height":0.028731046},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"bounds":{"left":0.50299203,"top":0.10055866,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":8,"bounds":{"left":0.50664896,"top":0.13806863,"width":0.009973404,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":6,"bounds":{"left":0.50299203,"top":0.15482841,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":8,"bounds":{"left":0.50764626,"top":0.19233839,"width":0.007978723,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":6,"bounds":{"left":0.50299203,"top":0.20909816,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":8,"bounds":{"left":0.5053192,"top":0.24660814,"width":0.012965426,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5053192,"top":0.24660814,"width":0.0026595744,"height":0.011173184}},{"char_start":1,"char_count":7,"bounds":{"left":0.50764626,"top":0.24660814,"width":0.010638298,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":6,"bounds":{"left":0.50299203,"top":0.26336792,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":8,"bounds":{"left":0.50797874,"top":0.3008779,"width":0.0076462766,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50797874,"top":0.3008779,"width":0.0019946808,"height":0.011173184}},{"char_start":1,"char_count":4,"bounds":{"left":0.5099734,"top":0.3008779,"width":0.0056515955,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":6,"bounds":{"left":0.50299203,"top":0.31763768,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8884484407622198276
|
-8120845959807158105
|
visual_change
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
sluckcolVIChRotonWindowHeir• =github.com/v Gooc e GeminiPipelines - jiminny/app(JY-20613) Allow owner's role to bAxUemered tines on New codJY-20963 fix deleted object impJiminny\Exceptions(EmailActivityfrInbox (1,734) - lukas.kovalik Sjlmiroonye Rosd ve php 81.15 dedPlatform Sorint 5.02 - Platform Te8 Jiminny7 (SRD-6881] (On demand) TransSona Strbramaal an at 27/05/2021aIliyana Netsova at 27/05/2026, 18) Jy 20910 schedule parallel upc XGemini 3.1 Pro ~That is a very common misconception, but it is not true anymoreWhile it is true that many vears ago around 2014-2016 phpredis had some buas regarding theorder in which EX and NX arguments were passed, it has fully supported combining them into asingle atomic command tor a very long timeTo do this atomically usina the phpredis driver, vou simply pass an associative array of options asthe third argumentHere is exactly how you would write it in your code:PHPSlockKey = sprintf(self::LOCK_KEY, Sthis->updateTarget):/l This executes a single, atomic sat coand with both NX and e)Sacquired = Redis::set(slockkey, true, L'NX, "EX => seLf::LOCK.TTL.SECONDSJ)8if (! Sacquired) &// Another process already holds the lockxeturn.I... report your metrics ...By relying on separate get, set, and expire commands, you are leaving a microsecond gapopen. In your new multi-process Supervisor architecture, two parallel workers can check the getboth see null, and both proceed to write the lock and push duplicate metrics, Usina the arravsyntax above closes that aao completely.A Cleaner laravel AlternativeSince vout are inside a laravel ann vou don't even nopd to ficht with driver-chorifir Redic suntayelcomintYour lim nou chate trhn" uend to imnroy our moddle Camioiie 4l1t cao mata mtake co donbls chack " Yoir oracy & CaminSummarize pageJy 20910 schedule paralCFilter tec.• E app|v = Component/ES|EntityTypeReindexer.phpReindexTargetStatsReporter.p3) UodateProcessManager.phpv Consolev ommande slactiecaaren• AsyncUpdateEsEntities.php•1AsvncUodateSupervisor.oho- DeleteEmailDocumentsCom…•)ReindexEntityTypes.php•1 RemoveGhostParticipantsCo.….8 ResetAsyncElasticSearchCo..2 Kernel.phpv= Contracts/2SStopSignalinterface.phpv 5 Traits2) GracefullvStonoable.ohov = tests/Unit/Component/ES•) EntityTvpeReindexerTest.php•) ReindexTaroetStatsReporterTes…8 UpdateProcessManagerTest.phpHoeAhiwitLateMoreiJiminnY ….inrede© Threads6d HuddlesDrafts & sent2) DirectoricAh External connections* Starreoolatform-backend…enlattormeinner.toam@) Channels# ai-chapten# alertshackend# bued# confusion-dlinid# donut time# enpineerine# general# hapoy birthdavA infosec interal all# infra-changest infrastructure del#rbu-team-into# liminny-be# olatform-team# platform-tickets# product launches"endom# relcasecundor# thank-yous# the peoole of fimill62 Direct messascwisiiwasemStefka Stovanova(e llivana Netsevala Stovan TomouPetko Kashinski01Inu co woy to.zU.U@ Describe what you are looking for* €. Vasil Vasileve MessagesNoR0d canvis7 Files* Pins +Пол не става, бех го направил така, и се сиvа)Dooywuukas Kovalnk Citororами и при мен се чумешеVasil Vasilev 3:11 PMoredis полльожа set key value.'nx a true, ex =60lconoreals he crisLukas Kovalik 3:PMтака май не работеше Sresult = Rediseset(StestKey, StestValue, 'EX, Stti, "NX*): а така да Sresult - Redisuset(StestKey, StestValue. ['nx, 'ex' => Sttll)Vasil Vasiley 3-11 PMlnkae Kovslil 242 omVasil Vasilev 3:12 PMобратното може да еltkac Kowalik 2412 01може и да брькамYasl Vaslley 342 pvt KOла иMаSlockAcquired - Redis::set(self::POLLING_LOCK,KEY, son_encode(SlockData), *EX", self::LOCK_TTL_SECONDS, *NX*)значи пьовото работиLukas Kovalik 3:13 PMла и в aop/Services/Crm/Hubspot/Glient.oho:103Vasil Vasilev 3:14 PMwe to ononssLukas Kovalik 3-16PMсега ги глелам, че и двете ми ги прави greved out полселните лва параметраVacil Vaciley 247 pvTukse Kouslly 2.47.01дано няма нещо промененоVacl Vacllev 2.17 pvHp зhaмTA CUCO NAaRA KakTO eLukas Kovalik 3:18PMок мисля ме є само 1dеMessage Vasil Vasiley- Aa...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85348
|
2923
|
3
|
2026-05-28T12:20:02.927440+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970802927_m2.jpg...
|
Slack
|
Activity - Jiminny Inc - 2 new items - Slack
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
All, 2 unread
DMs
Mentions
Threads
Add new tab
Vasil Vasilev
DM
Mark as unread
Clear
More actions
ще си го оставя както е
Stefka Stoyanova
DM
12 mins
извинявай, че така стана
Iliyana Netseva
DM
16 mins
ima li prichina tova da e napraveno po tozi nachin ili prosto taka e napraveno nqkoga
Stoyan Tomov
DM
1:17 PM
и имам и да добавя още нещо към него
Approved Toast
App
12:00 PM
#12136 JY-20915 fix missing header
#12136 JY-20915 fix missing header
by you
Petko Kashinski
DM
10:24 AM
Тенкс !
Galya Dimitrova
DM
10:14 AM
по скоро да ми пише за да видим дали ще го планираме
Petko Kashinski
Reacted in
DM...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"bounds":{"left":0.5056516,"top":0.058260176,"width":0.011968086,"height":0.028731046},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"bounds":{"left":0.50299203,"top":0.10055866,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":8,"bounds":{"left":0.50664896,"top":0.13806863,"width":0.009973404,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":6,"bounds":{"left":0.50299203,"top":0.15482841,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":8,"bounds":{"left":0.50764626,"top":0.19233839,"width":0.007978723,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":6,"bounds":{"left":0.50299203,"top":0.20909816,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":8,"bounds":{"left":0.5053192,"top":0.24660814,"width":0.012965426,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5053192,"top":0.24660814,"width":0.0026595744,"height":0.011173184}},{"char_start":1,"char_count":7,"bounds":{"left":0.50764626,"top":0.24660814,"width":0.010638298,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":6,"bounds":{"left":0.50299203,"top":0.26336792,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":8,"bounds":{"left":0.50797874,"top":0.3008779,"width":0.0076462766,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50797874,"top":0.3008779,"width":0.0019946808,"height":0.011173184}},{"char_start":1,"char_count":4,"bounds":{"left":0.5099734,"top":0.3008779,"width":0.0056515955,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":6,"bounds":{"left":0.50299203,"top":0.31763768,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":8,"bounds":{"left":0.50731385,"top":0.35514766,"width":0.008643617,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50731385,"top":0.35514766,"width":0.0019946808,"height":0.011173184}},{"char_start":1,"char_count":4,"bounds":{"left":0.5093085,"top":0.35514766,"width":0.0066489363,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":6,"bounds":{"left":0.50299203,"top":0.3719074,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":8,"bounds":{"left":0.50731385,"top":0.4094174,"width":0.008976064,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50731385,"top":0.4094174,"width":0.0033244682,"height":0.011173184}},{"char_start":1,"char_count":3,"bounds":{"left":0.5106383,"top":0.4094174,"width":0.0056515955,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"All, 2 unread","depth":11,"bounds":{"left":0.52892286,"top":0.09497207,"width":0.021609042,"height":0.026336791},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXRadioButton","text":"DMs","depth":11,"bounds":{"left":0.55019945,"top":0.09497207,"width":0.010970744,"height":0.026336791},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Mentions","depth":11,"bounds":{"left":0.56083775,"top":0.09497207,"width":0.010970744,"height":0.026336791},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Threads","depth":11,"bounds":{"left":0.57147604,"top":0.09497207,"width":0.010970744,"height":0.026336791},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Add new tab","depth":12,"bounds":{"left":0.58477396,"top":0.09497207,"width":0.0056515955,"height":0.026336791},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Vasil Vasilev","depth":15,"bounds":{"left":0.54787236,"top":0.1819633,"width":0.026595745,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.54787236,"top":0.1819633,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.55086434,"top":0.1819633,"width":0.023271276,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"bounds":{"left":0.54787236,"top":0.19872306,"width":0.00731383,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Mark as unread","depth":16,"bounds":{"left":0.5774601,"top":0.17717478,"width":0.010638298,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Clear","depth":16,"bounds":{"left":0.5880984,"top":0.17717478,"width":0.010638298,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":16,"bounds":{"left":0.5987367,"top":0.17717478,"width":0.010638298,"height":0.025538707},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"ще си го оставя както е","depth":16,"bounds":{"left":0.53324467,"top":0.21707901,"width":0.05418883,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.53324467,"top":0.21707901,"width":0.0043218085,"height":0.014365523}},{"char_start":1,"char_count":22,"bounds":{"left":0.5375665,"top":0.21707901,"width":0.049534574,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Stefka Stoyanova","depth":15,"bounds":{"left":0.54787236,"top":0.26177174,"width":0.037898935,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.54787236,"top":0.26177174,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.5505319,"top":0.26177174,"width":0.03523936,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"bounds":{"left":0.54787236,"top":0.27853152,"width":0.00731383,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"12 mins","depth":15,"bounds":{"left":0.5944149,"top":0.26336792,"width":0.014960106,"height":0.012769354},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5944149,"top":0.26336792,"width":0.0026595744,"height":0.013567438}},{"char_start":1,"char_count":6,"bounds":{"left":0.59674203,"top":0.26336792,"width":0.012632979,"height":0.013567438}}],"role_description":"text"},{"role":"AXStaticText","text":"извинявай, че така стана","depth":16,"bounds":{"left":0.53324467,"top":0.29688746,"width":0.05651596,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.53324467,"top":0.29688746,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":23,"bounds":{"left":0.5362367,"top":0.29688746,"width":0.053523935,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Iliyana Netseva","depth":15,"bounds":{"left":0.54787236,"top":0.3415802,"width":0.03324468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.54787236,"top":0.3415802,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":14,"bounds":{"left":0.54920214,"top":0.3415802,"width":0.031914894,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"bounds":{"left":0.54787236,"top":0.35834,"width":0.00731383,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"16 mins","depth":15,"bounds":{"left":0.5944149,"top":0.34317636,"width":0.014960106,"height":0.012769354},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5944149,"top":0.34317636,"width":0.0026595744,"height":0.013567438}},{"char_start":1,"char_count":6,"bounds":{"left":0.59674203,"top":0.34317636,"width":0.012632979,"height":0.013567438}}],"role_description":"text"},{"role":"AXStaticText","text":"ima li prichina tova da e napraveno po tozi nachin ili prosto taka e napraveno nqkoga","depth":16,"bounds":{"left":0.53324467,"top":0.37669593,"width":0.08643617,"height":0.049481247},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.53324467,"top":0.37669593,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":84,"bounds":{"left":0.53324467,"top":0.37669593,"width":0.084109046,"height":0.031923383}}],"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tomov","depth":15,"bounds":{"left":0.54787236,"top":0.43894652,"width":0.030917553,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.54787236,"top":0.43894652,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":11,"bounds":{"left":0.5505319,"top":0.43894652,"width":0.027925532,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"bounds":{"left":0.54787236,"top":0.4557063,"width":0.00731383,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1:17 PM","depth":15,"bounds":{"left":0.5930851,"top":0.4405427,"width":0.016289894,"height":0.012769354},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5930851,"top":0.4405427,"width":0.0026595744,"height":0.012769354}},{"char_start":1,"char_count":6,"bounds":{"left":0.59541225,"top":0.4405427,"width":0.013962766,"height":0.012769354}}],"role_description":"text"},{"role":"AXStaticText","text":"и имам и да добавя още нещо към него","depth":16,"bounds":{"left":0.53324467,"top":0.47406226,"width":0.08144947,"height":0.031923383},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.53324467,"top":0.47406226,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":35,"bounds":{"left":0.53324467,"top":0.47406226,"width":0.08144947,"height":0.031923383}}],"role_description":"text"},{"role":"AXStaticText","text":"Approved Toast","depth":15,"bounds":{"left":0.54787236,"top":0.5363129,"width":0.034242023,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.54787236,"top":0.5363129,"width":0.0033244682,"height":0.014365523}},{"char_start":1,"char_count":13,"bounds":{"left":0.5511968,"top":0.5363129,"width":0.030917553,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"App","depth":15,"bounds":{"left":0.54787236,"top":0.55307263,"width":0.007978723,"height":0.012769354},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.54787236,"top":0.55307263,"width":0.0029920214,"height":0.012769354}},{"char_start":1,"char_count":2,"bounds":{"left":0.55086434,"top":0.55307263,"width":0.004986702,"height":0.012769354}}],"role_description":"text"},{"role":"AXStaticText","text":"12:00 PM","depth":15,"bounds":{"left":0.59042555,"top":0.53790903,"width":0.018949468,"height":0.012769354},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.59042555,"top":0.53790903,"width":0.0026595744,"height":0.012769354}},{"char_start":1,"char_count":7,"bounds":{"left":0.5930851,"top":0.53790903,"width":0.016289894,"height":0.012769354}}],"role_description":"text"},{"role":"AXLink","text":"#12136 JY-20915 fix missing header","depth":16,"bounds":{"left":0.53324467,"top":0.5714286,"width":0.08111702,"height":0.014365523},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"#12136 JY-20915 fix missing header","depth":17,"bounds":{"left":0.53324467,"top":0.5714286,"width":0.08111702,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.53324467,"top":0.5714286,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":33,"bounds":{"left":0.5362367,"top":0.5714286,"width":0.078125,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"by you","depth":16,"bounds":{"left":0.53324467,"top":0.5714286,"width":0.082446806,"height":0.031923383},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Petko Kashinski","depth":15,"bounds":{"left":0.54787236,"top":0.63367915,"width":0.034574468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.54787236,"top":0.63367915,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":14,"bounds":{"left":0.55086434,"top":0.63367915,"width":0.03158245,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"bounds":{"left":0.54787236,"top":0.65043896,"width":0.00731383,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"10:24 AM","depth":15,"bounds":{"left":0.59042555,"top":0.63527536,"width":0.018949468,"height":0.012769354},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.59042555,"top":0.63527536,"width":0.0026595744,"height":0.012769354}},{"char_start":1,"char_count":7,"bounds":{"left":0.59275264,"top":0.63527536,"width":0.01662234,"height":0.012769354}}],"role_description":"text"},{"role":"AXStaticText","text":"Тенкс !","depth":16,"bounds":{"left":0.53324467,"top":0.6687949,"width":0.015292553,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.53324467,"top":0.6687949,"width":0.0023271276,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.5355718,"top":0.6687949,"width":0.012965426,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":15,"bounds":{"left":0.54787236,"top":0.7134876,"width":0.03523936,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.54787236,"top":0.7134876,"width":0.003656915,"height":0.014365523}},{"char_start":1,"char_count":14,"bounds":{"left":0.5515292,"top":0.7134876,"width":0.03158245,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"bounds":{"left":0.54787236,"top":0.7302474,"width":0.00731383,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"10:14 AM","depth":15,"bounds":{"left":0.59042555,"top":0.7150838,"width":0.018949468,"height":0.012769354},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.59042555,"top":0.7150838,"width":0.0026595744,"height":0.013567438}},{"char_start":1,"char_count":7,"bounds":{"left":0.59275264,"top":0.7150838,"width":0.01662234,"height":0.013567438}}],"role_description":"text"},{"role":"AXStaticText","text":"по скоро да ми пише за да видим дали ще го планираме","depth":16,"bounds":{"left":0.53324467,"top":0.74860334,"width":0.0787899,"height":0.031923383},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.53324467,"top":0.74860334,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":51,"bounds":{"left":0.53324467,"top":0.74860334,"width":0.0787899,"height":0.031923383}}],"role_description":"text"},{"role":"AXStaticText","text":"Petko Kashinski","depth":15,"bounds":{"left":0.54787236,"top":0.81085396,"width":0.034574468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.54787236,"top":0.81085396,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":14,"bounds":{"left":0.55086434,"top":0.81085396,"width":0.03158245,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Reacted in","depth":15,"bounds":{"left":0.54787236,"top":0.8276137,"width":0.019946808,"height":0.012769354},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.54787236,"top":0.8276137,"width":0.0026595744,"height":0.012769354}},{"char_start":1,"char_count":9,"bounds":{"left":0.5505319,"top":0.8276137,"width":0.017287234,"height":0.012769354}}],"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"bounds":{"left":0.56848407,"top":0.8276137,"width":0.00731383,"height":0.012769354},"on_screen":true,"role_description":"text"}]...
|
1626110335300586893
|
-3604818608052011901
|
click
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
All, 2 unread
DMs
Mentions
Threads
Add new tab
Vasil Vasilev
DM
Mark as unread
Clear
More actions
ще си го оставя както е
Stefka Stoyanova
DM
12 mins
извинявай, че така стана
Iliyana Netseva
DM
16 mins
ima li prichina tova da e napraveno po tozi nachin ili prosto taka e napraveno nqkoga
Stoyan Tomov
DM
1:17 PM
и имам и да добавя още нещо към него
Approved Toast
App
12:00 PM
#12136 JY-20915 fix missing header
#12136 JY-20915 fix missing header
by you
Petko Kashinski
DM
10:24 AM
Тенкс !
Galya Dimitrova
DM
10:14 AM
по скоро да ми пише за да видим дали ще го планираме
Petko Kashinski
Reacted in
DM
sluckcolVIChRotonWindowHeir• =github.com/v Gooc e GeminiPipelines - jiminny/app(JY-20613) Allow owner's role to bxUewredtines on New codJY-20963 fix deleted object impJiminny\Exceptions(EmailActivityfrInbox (1,734) - lukas.kovalik Sjlmiroonye Rosd ve php 81.15 dedPlatform Sorint 5.02 - Platform Te8 Jiminny7 (SRD-6881] (On demand) TransSona Strbramaal an at 27/05/2021aIliyana Netsova at 27/05/2026, 18) Jy 20910 schedule parallel upc XGemini 3.1 Pro ~That is a very common misconception, but it is not true anymoreWhile it is true that many vears ago (around 2014-2016 phpredis had some buas regarding theorder in which EX and NX arguments were passed, it has fully supported combining them into asingle atomic command tor a very long timeTo do this atomically usina the phpredis driver, vou simply pass an associative array of options asthe third argumentHere is exactly how you would write it in your code:PHPSlockKey = sprintf(self::LOCK_KEY, Sthis->updateTarget):/l This executes a single, atomic sat coand with both NX and e)Sacquired = Redis::set(slockkey, true, L'NX, "EX => seLf::LOCK.TTL-SECONDSJ)8if (! Sacquired) &// Another process already holds the lockxeturn.I... report your metrics ...By relying on separate get, set, and expire commands, you are leaving a microsecond gapopen. In your new multi-process Supervisor architecture, two parallel workers can check the get .both see null, and both proceed to write the lock and push duplicate metrics, Usina the arravsyntax above closes that aao completely.A Cleaner laravel AlternativeSince vout are inside a laravel ann vou don't even nopd to ficht with driver-chorifir Redic suntayelcomintYour lim nou chate trhn" uend to imnroy our moddle Camioiie 4l1t cao mata mtake co donbls chack " Yoir oracy & CaminSummarize pageinu co woy to.cu.@ Describe what you are looking forI1 OpenJy 20910 schedule paralCFilter tec.• E app|v = Component/ES|EntityTypeReindexer.phpReindexTargetStatsReporter.p3) UodateProcessManager.phpv Consolev ommande slactiecaaren• AsyncUpdateEsEntities.php•1AsvncUodateSupervisor.oho- DeleteEmailDocumentsCom…•)ReindexEntityTypes.php•1 RemoveGhostParticipantsCo..8 ResetAsyncElasticSearchCo..2 Kernel.phpv= Contracts/ESStopSignalInterface.phpv 5 Traits2) GracefullvStonoable.ohov = tests/Unit/Component/ES•) EntityTvpeReindexerTest.php•) ReindexTaroetStatsReporterTes…8) UpdateProcessManagerTest.phpHoesAhiwitLateMoreiAe&WIAYAll2UnreadsCo DMVasil Vasilevще си го оставя както сStefka Stoyanova6 OM12 mins €Io DMlliyana Netseva16 mins -ima li prichina tova da e napraveno potozi nachin ili prosto taka e napravenoStoyan Tomov1-17 PMO2 DMMuUsAUAS CARSRG MILG MOMIA MHeroeApproved Toas12-00 PMby youPetko Kachinck1024 AMocalvalle, mitrow1OX4AM6По СКоВо сЯ мИ ПИШе зА ОЯ ВИЛИМлали ше по планмоЯмАPetko KashinsklPosctadinou9-40AMYou: me nooseo.Yecterdh( Todor StamatovSelect a notification to view the details...
|
85346
|
NULL
|
NULL
|
NULL
|
|
85347
|
2922
|
2
|
2026-05-28T12:20:02.815976+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970802815_m1.jpg...
|
Slack
|
Activity - Jiminny Inc - 2 new items - Slack
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
All, 2 unread
DMs
Mentions
Threads
Add new tab
Vasil Vasilev
DM
Mark as unread
Clear
More actions
ще си го оставя както е
Stefka Stoyanova
DM
12 mins
извинявай, че така стана
Iliyana Netseva
DM
16 mins
ima li prichina tova da e napraveno po tozi nachin ili prosto taka e napraveno nqkoga
Stoyan Tomov
DM
1:17 PM
и имам и да добавя още нещо към него
Approved Toast
App
12:00 PM
#12136 JY-20915 fix missing header
#12136 JY-20915 fix missing header
by you
Petko Kashinski
DM
10:24 AM
Тенкс !
Galya Dimitrova
DM
10:14 AM
по скоро да ми пише за да видим дали ще го планираме
Petko Kashinski
Reacted in
DM
9:40 AM
You:
ще проверя
1 reaction, react with star2 emoji
1
Yesterday
Todor Stamatov
DM
Yesterday
готово
Steliyan Georgiev
DM
Yesterday
дай ми 3 мин и ще ти звънна
Steliyan Georgiev
Reacted in
DM
Yesterday
You:
само да видя къде може да се покаже
1 reaction, react with +1 emoji
1
Steliyan Georgiev
Reacted in
DM
Yesterday
You:
https://jiminny.atlassian.net/browse/JY-20613
https://jiminny.atlassian.net/browse/JY-20613
1 reaction, react with +1 emoji
1...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"All, 2 unread","depth":11,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXRadioButton","text":"DMs","depth":11,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Mentions","depth":11,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Threads","depth":11,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Add new tab","depth":12,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Vasil Vasilev","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Mark as unread","depth":16,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Clear","depth":16,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":16,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"ще си го оставя както е","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Stefka Stoyanova","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"12 mins","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"извинявай, че така стана","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Iliyana Netseva","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"16 mins","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ima li prichina tova da e napraveno po tozi nachin ili prosto taka e napraveno nqkoga","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tomov","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1:17 PM","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"и имам и да добавя още нещо към него","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Approved Toast","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"App","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"12:00 PM","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"#12136 JY-20915 fix missing header","depth":16,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"#12136 JY-20915 fix missing header","depth":17,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"by you","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Petko Kashinski","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"10:24 AM","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Тенкс !","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"10:14 AM","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"по скоро да ми пише за да видим дали ще го планираме","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Petko Kashinski","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Reacted in","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"9:40 AM","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"You:","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ще проверя","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"1 reaction, react with star2 emoji","depth":16,"on_screen":true,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":17,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Yesterday","depth":14,"bounds":{"left":0.6201389,"top":0.0,"width":0.039583333,"height":0.017777778},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Todor Stamatov","depth":15,"bounds":{"left":0.5798611,"top":0.0,"width":0.072222225,"height":0.02},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"bounds":{"left":0.5798611,"top":0.0,"width":0.015277778,"height":0.0033333334},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Yesterday","depth":15,"bounds":{"left":0.6701389,"top":0.0,"width":0.038194444,"height":0.017777778},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"готово","depth":16,"bounds":{"left":0.54930556,"top":0.0,"width":0.031944446,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":15,"bounds":{"left":0.5798611,"top":0.0,"width":0.07986111,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"bounds":{"left":0.5798611,"top":0.0,"width":0.015277778,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Yesterday","depth":15,"bounds":{"left":0.6701389,"top":0.0,"width":0.038194444,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"дай ми 3 мин и ще ти звънна","depth":16,"bounds":{"left":0.54930556,"top":0.0,"width":0.13958333,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":15,"bounds":{"left":0.5798611,"top":0.0,"width":0.07986111,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Reacted in","depth":15,"bounds":{"left":0.5798611,"top":0.0,"width":0.041666668,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"bounds":{"left":0.62291664,"top":0.0,"width":0.015277778,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Yesterday","depth":15,"bounds":{"left":0.6701389,"top":0.0,"width":0.038194444,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"You:","depth":16,"bounds":{"left":0.54930556,"top":0.0,"width":0.02013889,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"само да видя къде може да се покаже","depth":16,"bounds":{"left":0.54930556,"top":0.0,"width":0.17916666,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"1 reaction, react with +1 emoji","depth":16,"bounds":{"left":0.54930556,"top":0.0,"width":0.029861111,"height":0.0011111111},"on_screen":true,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":17,"bounds":{"left":0.56875,"top":0.0,"width":0.0048611113,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":15,"bounds":{"left":0.5798611,"top":0.0,"width":0.07986111,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Reacted in","depth":15,"bounds":{"left":0.5798611,"top":0.0,"width":0.041666668,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"DM","depth":15,"bounds":{"left":0.62291664,"top":0.0,"width":0.015277778,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Yesterday","depth":15,"bounds":{"left":0.6701389,"top":0.0,"width":0.038194444,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"You:","depth":16,"bounds":{"left":0.54930556,"top":0.0,"width":0.02013889,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"https://jiminny.atlassian.net/browse/JY-20613","depth":16,"bounds":{"left":0.54930556,"top":0.0,"width":0.17986111,"height":0.0011111111},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"https://jiminny.atlassian.net/browse/JY-20613","depth":17,"bounds":{"left":0.54930556,"top":0.0,"width":0.175,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"1 reaction, react with +1 emoji","depth":16,"bounds":{"left":0.54930556,"top":0.0,"width":0.029861111,"height":0.0011111111},"on_screen":true,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":17,"bounds":{"left":0.56875,"top":0.0,"width":0.0048611113,"height":0.0011111111},"on_screen":true,"role_description":"text"}]...
|
5897488005823127063
|
-2452178640716088950
|
click
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
All, 2 unread
DMs
Mentions
Threads
Add new tab
Vasil Vasilev
DM
Mark as unread
Clear
More actions
ще си го оставя както е
Stefka Stoyanova
DM
12 mins
извинявай, че така стана
Iliyana Netseva
DM
16 mins
ima li prichina tova da e napraveno po tozi nachin ili prosto taka e napraveno nqkoga
Stoyan Tomov
DM
1:17 PM
и имам и да добавя още нещо към него
Approved Toast
App
12:00 PM
#12136 JY-20915 fix missing header
#12136 JY-20915 fix missing header
by you
Petko Kashinski
DM
10:24 AM
Тенкс !
Galya Dimitrova
DM
10:14 AM
по скоро да ми пише за да видим дали ще го планираме
Petko Kashinski
Reacted in
DM
9:40 AM
You:
ще проверя
1 reaction, react with star2 emoji
1
Yesterday
Todor Stamatov
DM
Yesterday
готово
Steliyan Georgiev
DM
Yesterday
дай ми 3 мин и ще ти звънна
Steliyan Georgiev
Reacted in
DM
Yesterday
You:
само да видя къде може да се покаже
1 reaction, react with +1 emoji
1
Steliyan Georgiev
Reacted in
DM
Yesterday
You:
https://jiminny.atlassian.net/browse/JY-20613
https://jiminny.atlassian.net/browse/JY-20613
1 reaction, react with +1 emoji
1
PhpStormFileEditViewNavigateCodeRefactorRunToolsGitWindowHelpec2-user@ip-10-30-129-190:~screenpipe"X4‹$0(ah]БГ100% <78• Thu 28 May 15:20:02181ec2-user@ip-10-30-140-...₴7DOCKER#_####_#####\\###||\#/V~'881DEV (-zsh)O [EMAIL] Linux 2023 (ECS Optimized)_/m/For documentation, visit [URL_WITH_CREDENTIALS] ~]$ exitlogoutConnection to jiminny-prod-ecsi closed.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ applukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ vprodWarning: Permanently added 'jiminny-prod-ecs1' (ED25519) to the list of known hosts.A newer release of "Amazon Linux" is available.Version 2023.10.20260330:Version 2023.11.20260406:Version 2023.11.20260413:Version2023.11.20260427:Version2023.11.20260505:Version 2023.11.20260509:Version 2023.11.20260511:Version 2023.11.20260514:Run "/usr/bin/dnf check-release-update" for full release and version update info#_~\ ####_\ #####\\###1\#/Amazon Linux 2023 (ECS Optimized)/m/'For documentation, visit [URL_WITH_CREDENTIALS] ~]$ client_loop: send disconnect: Broken pipeukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-relay) $I...
|
85345
|
NULL
|
NULL
|
NULL
|
|
85346
|
2923
|
2
|
2026-05-28T12:20:00.393049+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970800393_m2.jpg...
|
Firefox
|
Jy 20910 schedule parallel update target processin Jy 20910 schedule parallel update target processing by Vasil-Jiminny · Pull Request #12130 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12130/changes
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
SIUCKFV faVsco.is ~Projectv© VoiceConsentPrefix.ph SIUCKFV faVsco.is ~Projectv© VoiceConsentPrefix.phpe Not iicationsnObservers© AccountObserver.php© ActivityObserver.php© ContactObserver.php© [EMAIL]© ProfileObserver.phpSocialAccountObserver.phpUserObserver.php© UserRoleObserver.php> Co PoliciesC ProvidersQueuea ReoositonieRutesvserviceActivityAiReodrEJ AvatarTeallendarVIChRotonWindowHelp#12121 on JY-20963-fox-hService.phppho helpers.phpDeleteObjectsTrait.phpOLeAkоkyscrco.onTeorcolmererestonC TextRelayServiceTest.phg(©) Hubspot.JournalPollinaService.ohp x$512Cass nuosoo vournatroto noseirv* Acquire exclusive polling lock to prevent aultiple instancesrivate function acquirePollingLock(): boolBullhorne copperimermobiacteDecorateActivityDummy› @ Helpersv im HibsodAccountSyncStrategyMAetioneContactSyncStrategy331332› DODTO› E Fieldswn taueea© HubspotClientCredenti339@HubspotDealWebhook 340© HubspotJournalApiCie341© HubspotJourmalPotingt 342© HubspotWebhookSubs© JournalApiResult.php© JournalßatchSizeLimitE© JournalEventTransforma MetadataOpportunitySyncStrategy→lmlpae natioo* Rerease the potting lockrivate function releasePollingLock(): voidl...* Force release the polling lock (for emergency situations)ublic function forceReleaseLock(): voidf...* Signat the polling service to stop gracefullyublic function requestStop(): void{...}Thu 28 May 15:19:59Q Describe what you are looking for©ActivityController.php© Kernel.phpE customlogA console (STAGING)# laravel.logA SFA12X9 A V—782733=713-234options: "EX', self::LOCK,717722TXS AutONANd a-created at > DATEGROUP BY u.id, v.emait,ORDER BY sne count DESCSELECTCoNcAl uIdo CAsc whsa.*,t.ouner_id FROM socieJOIN users u on u.id = siJoiN ceansWHERE u.tean_id = 1117 arSalae * rroh acavaesSELECT * FROM activitiossaee * FroN cra contaSELECT * FROM teans WHEREselleetron usens whenselect * fron playbooks vselect * fron playbook_ctselect * fron playbook_ceselect * fron crn_fieldsselect * fron crn_field.vSELECT * FROM crn_field_cAOTM con Brelds 4 0M 44• JOIN activities a ON $6WHERE activity_id = 7993;AND f.crn provider idSELECT * FROM activity_meselect * fron text_relaysselect * fron activitiesselect * fron users wherdselect x fron activitiesselect x fron accounts wiselect & tron users wherselect & fron teans wherselect & fron actvtysselect & fron actvitysHommeAe&WIAYAII 0C3 UnreadsVasil Vasilev2 minsще си го оставя както сStefka Stoyanova62 DM12 minlliyana Netseva16 minsA OMima li prichina tova da e napraveno potozi nachin ili prosto taka e napravenoStoyan Tomov1:17 PMи и да добавя още нещо къмorateApproved Toast12-00 PMby youPetko Kachinck1024 AMSelect a notification to view the details.chlvala. mitrow10.14 AM €По СКоВО сЯ МИ ПИШе ЗаГОЯ ВИЛИМлали ше по планмоЯмАPetko KashinskiPosstodin os9,40 AM03% Todor Stamatov...
|
NULL
|
6418315562933909899
|
NULL
|
visual_change
|
ocr
|
NULL
|
SIUCKFV faVsco.is ~Projectv© VoiceConsentPrefix.ph SIUCKFV faVsco.is ~Projectv© VoiceConsentPrefix.phpe Not iicationsnObservers© AccountObserver.php© ActivityObserver.php© ContactObserver.php© [EMAIL]© ProfileObserver.phpSocialAccountObserver.phpUserObserver.php© UserRoleObserver.php> Co PoliciesC ProvidersQueuea ReoositonieRutesvserviceActivityAiReodrEJ AvatarTeallendarVIChRotonWindowHelp#12121 on JY-20963-fox-hService.phppho helpers.phpDeleteObjectsTrait.phpOLeAkоkyscrco.onTeorcolmererestonC TextRelayServiceTest.phg(©) Hubspot.JournalPollinaService.ohp x$512Cass nuosoo vournatroto noseirv* Acquire exclusive polling lock to prevent aultiple instancesrivate function acquirePollingLock(): boolBullhorne copperimermobiacteDecorateActivityDummy› @ Helpersv im HibsodAccountSyncStrategyMAetioneContactSyncStrategy331332› DODTO› E Fieldswn taueea© HubspotClientCredenti339@HubspotDealWebhook 340© HubspotJournalApiCie341© HubspotJourmalPotingt 342© HubspotWebhookSubs© JournalApiResult.php© JournalßatchSizeLimitE© JournalEventTransforma MetadataOpportunitySyncStrategy→lmlpae natioo* Rerease the potting lockrivate function releasePollingLock(): voidl...* Force release the polling lock (for emergency situations)ublic function forceReleaseLock(): voidf...* Signat the polling service to stop gracefullyublic function requestStop(): void{...}Thu 28 May 15:19:59Q Describe what you are looking for©ActivityController.php© Kernel.phpE customlogA console (STAGING)# laravel.logA SFA12X9 A V—782733=713-234options: "EX', self::LOCK,717722TXS AutONANd a-created at > DATEGROUP BY u.id, v.emait,ORDER BY sne count DESCSELECTCoNcAl uIdo CAsc whsa.*,t.ouner_id FROM socieJOIN users u on u.id = siJoiN ceansWHERE u.tean_id = 1117 arSalae * rroh acavaesSELECT * FROM activitiossaee * FroN cra contaSELECT * FROM teans WHEREselleetron usens whenselect * fron playbooks vselect * fron playbook_ctselect * fron playbook_ceselect * fron crn_fieldsselect * fron crn_field.vSELECT * FROM crn_field_cAOTM con Brelds 4 0M 44• JOIN activities a ON $6WHERE activity_id = 7993;AND f.crn provider idSELECT * FROM activity_meselect * fron text_relaysselect * fron activitiesselect * fron users wherdselect x fron activitiesselect x fron accounts wiselect & tron users wherselect & fron teans wherselect & fron actvtysselect & fron actvitysHommeAe&WIAYAII 0C3 UnreadsVasil Vasilev2 minsще си го оставя както сStefka Stoyanova62 DM12 minlliyana Netseva16 minsA OMima li prichina tova da e napraveno potozi nachin ili prosto taka e napravenoStoyan Tomov1:17 PMи и да добавя още нещо къмorateApproved Toast12-00 PMby youPetko Kachinck1024 AMSelect a notification to view the details.chlvala. mitrow10.14 AM €По СКоВО сЯ МИ ПИШе ЗаГОЯ ВИЛИМлали ше по планмоЯмАPetko KashinskiPosstodin os9,40 AM03% Todor Stamatov...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85345
|
2922
|
1
|
2026-05-28T12:19:58.074892+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970798074_m1.jpg...
|
Slack
|
Vasil Vasilev (DM) - Jiminny Inc - 2 new items - S Vasil Vasilev (DM) - Jiminny Inc - 2 new items - Slack...
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
Stoyan Tomov
Petko Kashinski
Galya Dimitrova
Todor Stamatov
Steliyan Georgiev
Ves
Mira...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"platform-backend-engineers","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"bugs","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"donut_time","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"general","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"happy_birthday","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"infosec_internal_all","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"infra-changes","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"infrastructure_dev","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"jbu-team-info","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"platform-team","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"random","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"support","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Stefka Stoyanova","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Iliyana Netseva","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tomov","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Petko Kashinski","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Todor Stamatov","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Ves","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Mira","depth":15,"on_screen":true,"role_description":"text"}]...
|
4089159406747339628
|
-5801243608421254418
|
click
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
Stoyan Tomov
Petko Kashinski
Galya Dimitrova
Todor Stamatov
Steliyan Georgiev
Ves
Mira
PhpStormFileEditViewNavigateCodeRefactorRunToolsGitWindowHelpec2-user@ip-10-30-129-190:~screenpipe"O ₴4‹$0(ah]БГ100% <78• Thu 28 May 15:19:57181ec2-user@ip-10-30-140-…₴7DOCKER#_####_#####\\###||\#/V~'881DEV (-zsh)O [EMAIL] Linux 2023 (ECS Optimized)_/m/For documentation, visit [URL_WITH_CREDENTIALS] ~]$ exitlogoutConnection to jiminny-prod-ecsi closed.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ applukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ vprodWarning: Permanently added 'jiminny-prod-ecs1' (ED25519) to the list of known hosts.A newer release of "Amazon Linux" is available.Version 2023.10.20260330:Version 2023.11.20260406:Version 2023.11.20260413:Version2023.11.20260427:Version2023.11.20260505:Version 2023.11.20260509:Version 2023.11.20260511:Version 2023.11.20260514:Run "/usr/bin/dnf check-release-update" for full release and version update info#_~\ ####_\ #####\\###1\#/Amazon Linux 2023 (ECS Optimized)/m/'For documentation, visit [URL_WITH_CREDENTIALS] ~]$ client_loop: send disconnect: Broken pipeukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-relay) $I...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85344
|
2923
|
1
|
2026-05-28T12:19:57.969323+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970797969_m2.jpg...
|
Slack
|
Vasil Vasilev (DM) - Jiminny Inc - 2 new items - S Vasil Vasilev (DM) - Jiminny Inc - 2 new items - Slack...
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
Stoyan Tomov
Petko Kashinski...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"bounds":{"left":0.5056516,"top":0.058260176,"width":0.011968086,"height":0.028731046},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"bounds":{"left":0.50299203,"top":0.10055866,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":8,"bounds":{"left":0.50664896,"top":0.13806863,"width":0.009973404,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":6,"bounds":{"left":0.50299203,"top":0.15482841,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":8,"bounds":{"left":0.50764626,"top":0.19233839,"width":0.007978723,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":6,"bounds":{"left":0.50299203,"top":0.20909816,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":8,"bounds":{"left":0.5053192,"top":0.24660814,"width":0.012965426,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5053192,"top":0.24660814,"width":0.0026595744,"height":0.011173184}},{"char_start":1,"char_count":7,"bounds":{"left":0.50764626,"top":0.24660814,"width":0.010638298,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":6,"bounds":{"left":0.50299203,"top":0.26336792,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":8,"bounds":{"left":0.50797874,"top":0.3008779,"width":0.0076462766,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50797874,"top":0.3008779,"width":0.0019946808,"height":0.011173184}},{"char_start":1,"char_count":4,"bounds":{"left":0.5099734,"top":0.3008779,"width":0.0056515955,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":6,"bounds":{"left":0.50299203,"top":0.31763768,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":8,"bounds":{"left":0.50731385,"top":0.35514766,"width":0.008643617,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50731385,"top":0.35514766,"width":0.0019946808,"height":0.011173184}},{"char_start":1,"char_count":4,"bounds":{"left":0.5093085,"top":0.35514766,"width":0.0066489363,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":6,"bounds":{"left":0.50299203,"top":0.3719074,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":8,"bounds":{"left":0.50731385,"top":0.4094174,"width":0.008976064,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50731385,"top":0.4094174,"width":0.0033244682,"height":0.011173184}},{"char_start":1,"char_count":3,"bounds":{"left":0.5106383,"top":0.4094174,"width":0.0056515955,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"platform-backend-engineers","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":15,"bounds":{"left":0.5422208,"top":0.09736632,"width":0.018284574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.09736632,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54521275,"top":0.09736632,"width":0.015292553,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"bugs","depth":15,"bounds":{"left":0.5422208,"top":0.11971269,"width":0.010638298,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.11971269,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":3,"bounds":{"left":0.54521275,"top":0.11971269,"width":0.00731383,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":15,"bounds":{"left":0.5422208,"top":0.14205906,"width":0.034574468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.14205906,"width":0.0023271276,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54454786,"top":0.14205906,"width":0.032247342,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"donut_time","depth":15,"bounds":{"left":0.5422208,"top":0.16440542,"width":0.025265958,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.16440542,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54521275,"top":0.16440542,"width":0.022273935,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":15,"bounds":{"left":0.5422208,"top":0.1867518,"width":0.025930852,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.1867518,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":10,"bounds":{"left":0.54488033,"top":0.1867518,"width":0.022938829,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"general","depth":15,"bounds":{"left":0.5422208,"top":0.20909816,"width":0.016289894,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.20909816,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54488033,"top":0.20909816,"width":0.013297873,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"happy_birthday","depth":15,"bounds":{"left":0.5422208,"top":0.23144454,"width":0.033909574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.23144454,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":13,"bounds":{"left":0.54521275,"top":0.23144454,"width":0.030917553,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"infosec_internal_all","depth":15,"bounds":{"left":0.5422208,"top":0.25379092,"width":0.041888297,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.25379092,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":19,"bounds":{"left":0.54355055,"top":0.25379092,"width":0.040226065,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"infra-changes","depth":15,"bounds":{"left":0.5422208,"top":0.27613726,"width":0.029587766,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.27613726,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54355055,"top":0.27613726,"width":0.02825798,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"infrastructure_dev","depth":15,"bounds":{"left":0.5422208,"top":0.29848364,"width":0.040226065,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.29848364,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":17,"bounds":{"left":0.54355055,"top":0.29848364,"width":0.038896278,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"jbu-team-info","depth":15,"bounds":{"left":0.5422208,"top":0.32083002,"width":0.029920213,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.32083002,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54355055,"top":0.32083002,"width":0.028590426,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":15,"bounds":{"left":0.5422208,"top":0.34317636,"width":0.023271276,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.34317636,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54355055,"top":0.34317636,"width":0.021609042,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"platform-team","depth":15,"bounds":{"left":0.5422208,"top":0.36552274,"width":0.03158245,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.36552274,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54521275,"top":0.36552274,"width":0.028590426,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":15,"bounds":{"left":0.5422208,"top":0.38786912,"width":0.034906916,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.38786912,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54521275,"top":0.38786912,"width":0.031914894,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":15,"bounds":{"left":0.5422208,"top":0.4102155,"width":0.03856383,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.4102155,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54521275,"top":0.4102155,"width":0.03557181,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"random","depth":15,"bounds":{"left":0.5422208,"top":0.43256184,"width":0.016954787,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.43256184,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":5,"bounds":{"left":0.54421544,"top":0.43256184,"width":0.014960106,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":15,"bounds":{"left":0.5422208,"top":0.45490822,"width":0.018284574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.45490822,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":7,"bounds":{"left":0.54421544,"top":0.45490822,"width":0.016289894,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"support","depth":15,"bounds":{"left":0.5422208,"top":0.4772546,"width":0.017287234,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.4772546,"width":0.0023271276,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54454786,"top":0.4772546,"width":0.01462766,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":15,"bounds":{"left":0.5422208,"top":0.49960095,"width":0.024601065,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.49960095,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54421544,"top":0.49960095,"width":0.022606382,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":15,"bounds":{"left":0.5422208,"top":0.5219473,"width":0.045212764,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.5219473,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":20,"bounds":{"left":0.54421544,"top":0.5219473,"width":0.04720745,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":15,"bounds":{"left":0.5422208,"top":0.5746209,"width":0.026595745,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.5746209,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54521275,"top":0.5746209,"width":0.023271276,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Stefka Stoyanova","depth":15,"bounds":{"left":0.5422208,"top":0.5969673,"width":0.037898935,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.5969673,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54488033,"top":0.5969673,"width":0.03523936,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Iliyana Netseva","depth":15,"bounds":{"left":0.5422208,"top":0.61931366,"width":0.03324468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.61931366,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":14,"bounds":{"left":0.54355055,"top":0.61931366,"width":0.031914894,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tomov","depth":15,"bounds":{"left":0.5422208,"top":0.64166003,"width":0.030917553,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.64166003,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":11,"bounds":{"left":0.54488033,"top":0.64166003,"width":0.027925532,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Petko Kashinski","depth":15,"bounds":{"left":0.5422208,"top":0.6640064,"width":0.034574468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.6640064,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":14,"bounds":{"left":0.54521275,"top":0.6640064,"width":0.03158245,"height":0.014365523}}],"role_description":"text"}]...
|
-3670568355441726966
|
-5801243608388223250
|
click
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
Stoyan Tomov
Petko Kashinski
SIUCKFV faVsco.is ~Projectv© VoiceConsentPrefix.phpe Not iicationsnObservers© AccountObserver.php© ActivityObserver.php© ContactObserver.php© [EMAIL]© ProfileObserver.phpSocialAccountObserver.phpUserObserver.php© UserRoleObserver.php> Co PoliciesC ProvidersQueueu ReoositonieCRutesvserviceActivityAiReodrEJ AvatarlealendarVIChRotonWindowHelp#12121 on JY-20963-fox-hService.phpphe helpers.phpDeleteObjectsTrait.phpOLeAkоkyscrco.onTeareolaocererestonC TextRelayServiceTest.phg(©) Hubspot.JournalPollinaService.ohp x$512Cass nuosoo vourhalroto hoseiv* Acquire exclusive polling lock to prevent aultiple instancesrivate function acquirePollingLock(): boolBullhorne copperimermobiacteDecorateActivityDummy› @ Helpersv im HibsodAccountSyncStrategyIhctiontContactSyncStrategy331332› DODTO› E Fieldswn taueea© HubspotClientCredenti339@HubspotDealWebhook 340© HubspotJournalApiCie341© HubspotJourmalPotingt 342© HubspotWebhookSubs© JournalApiResult.php© JournalßatchSizeLimitE© JournalEventTransforma MetadataOpportunitySyncStrategylmlpas nation* Rerease the potting lockrivate function releasePollingLock(): voidf...,* Force release the polling lock (for emergency situations)ublic function forceReleaseLock(): voidf...* Signat the polling service to stop gracefullyublic function requestStop(): void{...}©ActivityController.php© Kernel.phpE customlogA console (STAGING)# laravel.logA12X9 A V—782783704705786709710711712=713-74715self:: LOCK,717720722722723ABREED732735734735736737738TX: AUTONAND a.created_at > DATE_SGROUP BY u.id, v.email,ORDER RY sns count 0ESCselect * fron teans wherdselect * fron roles:SELECTCONCAT(U.1d, CASE WHEsa.*,t.ouner_id FROM socieJOIN users u on u.id = stJOIN teans t 1n<-›1: onWHERE v.tean_id = 1117 arSELECT * FROM activitiesSELECT * FROM activitiosSELECT * FROM crn_configuSELECT * FROM teans WHERHseleerron usens wherselect * fron playbooksselect * fron playbook_caselect * fron playbook_ctselect * fron crn_fieldsselect * fron crn_field.vSELECT * FROM crn_field_c* JOIN crn_fields f ON fo• JOIN activities a ON $6WHERE activity_id = 7993:AND f.crn_provider idSELECT * FROM activity_meselect * fron text_relaysselect * fron activitiesselect * fron users wherdselect x fron activitiesselect * fron accounts wiselect * fron users wherdselect x fron teans wherdselect x fron activity_stselect x fron activity_stHomeDMsActivity00LaterMonJiminnY ….# backend# bugs# confusion-clinic# donut_time# engineering# general# happy_birthday8 infosec_internal_all# infra-changes# infrastructure_dev# jbu-team-info#jiminny-bg# platform-team# platform-ticketstoroduct launche# random# releases# support# thank-yous# the people of jimi..a? Direct messages€. Vasil Vasilev8. Stefka Stoyanova[. lliyana Netseva88. Stoyan Tomov® Petko KashinskiD, Galya Dimitrova E: Todor Stamatov. Steliyan Georgiev8 Ves(R. Mira. Nikolay Yankovdo James Grahaminu co moy to.laioQ Describe what you are looking for€. Vasil Vasileve MessagerNoR0d canvis© Files& Pins +Пея не става, бях го направил така, и се счупиLukas Kovalik 3:10 PMами и при мен се чумешеVasil Vasilev 3:11 PMpredis полльожа set(Skey. Svalue. "'nx' - true. ex =60)Dooywconoreals he crisLukas Kovalik 3:11 PMVasil Vasilev 3:11 PMтака май не работеше Sresult - Redis::set(StestKey. StestValue, 'EX, Stti, 'NX): а така да Sresult - Redis:set(StestKey. StestValue, ['nx, 'ex' => $tti]*Lukas Kovalik 3:12PMNo naune soudVasil Vasilev 3:12 PMобратното може да еtkac.Kow.ik 2412oMможе и да брькамVasil Vasilev 3:12 PMT KONh ИMASlockAcquired - Redis::set(self::POLLING_LOCK,KEY, Json_encode(SlockData), *EX", self::LOCK_TTL_SECONDS, *NX*)значи пьовото работиLukas Kovalik 313 PMда и в арр/Services/Crm/Hubspot/Client.php:103Vasil Vasilev 3:14 PMLukas Kovalik 3:16 PMсега ги глелам, че и двете ми ги прави greved out полселните лва параметраVasil Vasilev 3:17 PMMusMoLukas Kovalik 3:17 PMдано няма нещо промененоVasil Vasilev 3:17 PMне знам1Oe0...
|
85342
|
NULL
|
NULL
|
NULL
|
|
85343
|
2922
|
0
|
2026-05-28T12:19:29.486305+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970769486_m1.jpg...
|
Slack
|
Vasil Vasilev (DM) - Jiminny Inc - 2 new items - S Vasil Vasilev (DM) - Jiminny Inc - 2 new items - Slack...
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
Stoyan Tomov
Petko Kashinski
Galya Dimitrova
Todor Stamatov
Steliyan Georgiev
Ves
Mira
Nikolay Yankov
James Graham
Lukas Kovalik
you
Jira Cloud
Toast
Google Calendar
Messages
Messages
Add canvas
Add canvas
Files
Files
Pins
Pins
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
Lukas Kovalik
Today at 2:45:12 PM
2:45 PM
Не знам това дали е релевантно
Today at 2:45:14 PM
2:45
1.
Shared Elasticsearch Sockets:
Because
pcntl_fork()
clones file descriptors, both child processes will share the exact same HTTP/TCP socket for Elasticsearch. Since you didn’t reset the ES client in
resetAfterFork()
(like you did for Redis and DB), the parallel workers will collide at the network layer, causing corrupted data and
cURL
exceptions.
2.
Supervisor Shutdown Deadlock:
In
reapChildren()
, calling
pcntl_waitpid()
without the
WNOHANG...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"platform-backend-engineers","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"bugs","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"donut_time","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"general","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"happy_birthday","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"infosec_internal_all","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"infra-changes","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"infrastructure_dev","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"jbu-team-info","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"platform-team","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"random","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"support","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Stefka Stoyanova","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Iliyana Netseva","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tomov","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Petko Kashinski","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Todor Stamatov","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Ves","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Mira","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"James Graham","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Lukas Kovalik","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"you","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Jira Cloud","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Toast","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Google Calendar","depth":15,"bounds":{"left":0.56805557,"top":0.0,"width":0.06388889,"height":0.02},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.56805557,"top":0.0,"width":0.007638889,"height":0.02}},{"char_start":1,"char_count":14,"bounds":{"left":0.57569444,"top":0.0,"width":0.06875,"height":0.02}}],"role_description":"text"},{"role":"AXRadioButton","text":"Messages","depth":9,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Messages","depth":11,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Add canvas","depth":10,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Add canvas","depth":12,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":9,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":11,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Pins","depth":9,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Pins","depth":11,"on_screen":true,"role_description":"text"},{"role":"AXPopUpButton","text":"Add and Edit Channel Tabs","depth":9,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Canvas","depth":9,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"List","depth":9,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":9,"on_screen":true,"role_description":"text"},{"role":"AXPopUpButton","text":"Jump to date","depth":15,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Lukas Kovalik","depth":16,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"Today at 2:45:12 PM","depth":16,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2:45 PM","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Не знам това дали е релевантно","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"Today at 2:45:14 PM","depth":17,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2:45","depth":18,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"1.","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Shared Elasticsearch Sockets:","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Because","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"pcntl_fork()","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"clones file descriptors, both child processes will share the exact same HTTP/TCP socket for Elasticsearch. Since you didn’t reset the ES client in","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"resetAfterFork()","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"(like you did for Redis and DB), the parallel workers will collide at the network layer, causing corrupted data and","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"cURL","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"exceptions.","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"2.","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Supervisor Shutdown Deadlock:","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"In","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"reapChildren()","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":", calling","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"pcntl_waitpid()","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"without the","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"WNOHANG","depth":17,"on_screen":false,"role_description":"text"}]...
|
1201104130349314360
|
-5828336945636336922
|
idle
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
Stoyan Tomov
Petko Kashinski
Galya Dimitrova
Todor Stamatov
Steliyan Georgiev
Ves
Mira
Nikolay Yankov
James Graham
Lukas Kovalik
you
Jira Cloud
Toast
Google Calendar
Messages
Messages
Add canvas
Add canvas
Files
Files
Pins
Pins
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
Lukas Kovalik
Today at 2:45:12 PM
2:45 PM
Не знам това дали е релевантно
Today at 2:45:14 PM
2:45
1.
Shared Elasticsearch Sockets:
Because
pcntl_fork()
clones file descriptors, both child processes will share the exact same HTTP/TCP socket for Elasticsearch. Since you didn’t reset the ES client in
resetAfterFork()
(like you did for Redis and DB), the parallel workers will collide at the network layer, causing corrupted data and
cURL
exceptions.
2.
Supervisor Shutdown Deadlock:
In
reapChildren()
, calling
pcntl_waitpid()
without the
WNOHANG
PhpStormFileEditViewNavigateCodeRefactorRunToolsGitWindowHelpec2-user@ip-10-30-129-190:~screenpipe"O ₴4‹$0(ah]БГ100% <78• Thu 28 May 15:19:29181ec2-user@ip-10-30-140-….₴7DOCKER#_####_#####\\###||\#/V~'881DEV (-zsh)O [EMAIL] Linux 2023 (ECS Optimized)_/m/For documentation, visit [URL_WITH_CREDENTIALS] ~]$ exitlogoutConnection to jiminny-prod-ecsi closed.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ applukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ vprodWarning: Permanently added 'jiminny-prod-ecs1' (ED25519) to the list of known hosts.A newer release of "Amazon Linux" is available.Version 2023.10.20260330:Version 2023.11.20260406:Version 2023.11.20260413:Version2023.11.20260427:Version2023.11.20260505:Version 2023.11.20260509:Version 2023.11.20260511:Version 2023.11.20260514:Run "/usr/bin/dnf check-release-update" for full release and version update info#_~\ ####_\ #####\\###1\#/Amazon Linux 2023 (ECS Optimized)/m/'For documentation, visit [URL_WITH_CREDENTIALS] ~]$ client_loop: send disconnect: Broken pipeukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-relay) $I...
|
85341
|
NULL
|
NULL
|
NULL
|
|
85342
|
2923
|
0
|
2026-05-28T12:19:29.166836+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970769166_m2.jpg...
|
Slack
|
Vasil Vasilev (DM) - Jiminny Inc - 2 new items - S Vasil Vasilev (DM) - Jiminny Inc - 2 new items - Slack...
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"bounds":{"left":0.5056516,"top":0.058260176,"width":0.011968086,"height":0.028731046},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"bounds":{"left":0.50299203,"top":0.10055866,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":8,"bounds":{"left":0.50664896,"top":0.13806863,"width":0.009973404,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":6,"bounds":{"left":0.50299203,"top":0.15482841,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":8,"bounds":{"left":0.50764626,"top":0.19233839,"width":0.007978723,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":6,"bounds":{"left":0.50299203,"top":0.20909816,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":8,"bounds":{"left":0.5053192,"top":0.24660814,"width":0.012965426,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5053192,"top":0.24660814,"width":0.0026595744,"height":0.011173184}},{"char_start":1,"char_count":7,"bounds":{"left":0.50764626,"top":0.24660814,"width":0.010638298,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":6,"bounds":{"left":0.50299203,"top":0.26336792,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":8,"bounds":{"left":0.50797874,"top":0.3008779,"width":0.0076462766,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50797874,"top":0.3008779,"width":0.0019946808,"height":0.011173184}},{"char_start":1,"char_count":4,"bounds":{"left":0.5099734,"top":0.3008779,"width":0.0056515955,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":6,"bounds":{"left":0.50299203,"top":0.31763768,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":8,"bounds":{"left":0.50731385,"top":0.35514766,"width":0.008643617,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50731385,"top":0.35514766,"width":0.0019946808,"height":0.011173184}},{"char_start":1,"char_count":4,"bounds":{"left":0.5093085,"top":0.35514766,"width":0.0066489363,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":6,"bounds":{"left":0.50299203,"top":0.3719074,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":8,"bounds":{"left":0.50731385,"top":0.4094174,"width":0.008976064,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50731385,"top":0.4094174,"width":0.0033244682,"height":0.011173184}},{"char_start":1,"char_count":3,"bounds":{"left":0.5106383,"top":0.4094174,"width":0.0056515955,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"platform-backend-engineers","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":15,"bounds":{"left":0.5422208,"top":0.09736632,"width":0.018284574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.09736632,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54521275,"top":0.09736632,"width":0.015292553,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"bugs","depth":15,"bounds":{"left":0.5422208,"top":0.11971269,"width":0.010638298,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.11971269,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":3,"bounds":{"left":0.54521275,"top":0.11971269,"width":0.00731383,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":15,"bounds":{"left":0.5422208,"top":0.14205906,"width":0.034574468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.14205906,"width":0.0023271276,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54454786,"top":0.14205906,"width":0.032247342,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"donut_time","depth":15,"bounds":{"left":0.5422208,"top":0.16440542,"width":0.025265958,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.16440542,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54521275,"top":0.16440542,"width":0.022273935,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":15,"bounds":{"left":0.5422208,"top":0.1867518,"width":0.025930852,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.1867518,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":10,"bounds":{"left":0.54488033,"top":0.1867518,"width":0.022938829,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"general","depth":15,"bounds":{"left":0.5422208,"top":0.20909816,"width":0.016289894,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.20909816,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54488033,"top":0.20909816,"width":0.013297873,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"happy_birthday","depth":15,"bounds":{"left":0.5422208,"top":0.23144454,"width":0.033909574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.23144454,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":13,"bounds":{"left":0.54521275,"top":0.23144454,"width":0.030917553,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"infosec_internal_all","depth":15,"bounds":{"left":0.5422208,"top":0.25379092,"width":0.041888297,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.25379092,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":19,"bounds":{"left":0.54355055,"top":0.25379092,"width":0.040226065,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"infra-changes","depth":15,"bounds":{"left":0.5422208,"top":0.27613726,"width":0.029587766,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.27613726,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54355055,"top":0.27613726,"width":0.02825798,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"infrastructure_dev","depth":15,"bounds":{"left":0.5422208,"top":0.29848364,"width":0.040226065,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.29848364,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":17,"bounds":{"left":0.54355055,"top":0.29848364,"width":0.038896278,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"jbu-team-info","depth":15,"bounds":{"left":0.5422208,"top":0.32083002,"width":0.029920213,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.32083002,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54355055,"top":0.32083002,"width":0.028590426,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":15,"bounds":{"left":0.5422208,"top":0.34317636,"width":0.023271276,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.34317636,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54355055,"top":0.34317636,"width":0.021609042,"height":0.014365523}}],"role_description":"text"}]...
|
7672148898894633558
|
-1165139292133835017
|
idle
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
SIUCKFV faVsco.is ~Projectv© VoiceConsentPrefix.phpen NoticationsnObservers© AccountObserver.php© ActivityObserver.php© ContactObserver.php© [EMAIL]© ProfileObserver.phpSocialAccountObserver.phpUserObserver.php© UserRoleObserver.php> Co PoliciesC ProvidersQueuea ReoositonieCRutesvserviceActivityAiReodrEJ AvatarlealendarVIChRotonWindowHelp#12121 on JY-20963-fox-hService.phppho helpers.phpDeleteObjectsTrait.phpOLeAkоkyScrco.on8 UserRoleObserverTest.phpC TextRelayServiceTest.phg(©) Hubspot.JournalPollinaService.ohp x*, 88Cass nuosoo vourhalroto hoseiv* Acquire exclusive polling lock to prevent aultiple instancesS &rivate function acquirePollingLock(): boolBullhorne copperimermobiacteDecorateActivityDummy› @ Helpersv im HibsodAccountSyncStrategyMAetioneContactSyncStrategy› DODTO331333› E Fieldswn taueea© HubspotClientCredenti339@HubspotDealWebhook 340© HubspotJournalApiCie341© HubspotJournalPolling: 342© HubspotWebhookSubs© JournalApiResult.php© JournalßatchSizeLimitE© JournalEventTransforma MetadataOpportunitySyncStrategy→lmlpae natioo* Rerease the potting lockrivate function releasePollingLock(): voidf...,* Force release the polling lock (for emergency situations)ublic function forceReleaseLock(): voidf...* Signat the polling service to stop gracefullyublic function requestStop(): void{...}©ActivityController.php© Kernel.phpE custom.logA console (STAGING)# laravel.logA12X9 A V-701-78278370478S786709710733712=713-74715self:: LOCK,717"728722722723-726PRRRE732733734735736737TX: AUTONAND a.created_at > DATE_SGROUP BY u.id, v.email,ORDER RY sns count 0ESCHommeselect * fron teans whereDMsselect * fron roles:ActivitySELECTCONCAT(U.1d, CASE WHEsa.*,t.ouner_id FROM socieJOIN users u on u.id = stJOIN teans t 1n<-›1: onWHERE v.tean_id = 1117 arSELECT * FROM activitiesSELECT * FROM activitiosSELECT * FROM crn_configuSELECT * FROM teans WHERHseleerron usens wherselect * fron playbooksselect * fron playbook_caselect * fron playbook_ctselect * fron crn_fieldsselect * fron crn_field.vSELECT * FROM Crn_field_c* JOIN crn_fields f ON fc• JOIN activities a ON $6WHERE activity_id = 7993;AND f.crn_provider idLaterMonSELECT * FROM activity_moselect * fron text_relaysselect * fron activitiesselect * fron users wherdselect x fron activitiesselect * fron accounts wiselect * fron users wherdselect x fron teans wherdselect x fron activity_stselect x fron activity_stJiminnY …# backend# bugs# confusion-clinic# donut_time# engineering# general# happy_birthday8 infosec_internal_all# infra-changes# infrastructure_dev# jbu-team-info#jiminny-bg# platform-team# platform-ticketstoroduct launche# random# releases# support# thank-yous# the people of jimi..a? Direct messages€. Vasil Vasilev8. Stefka Stoyanova[. lliyana Netseva88. Stoyan Tomov® Petko KashinskiD, Galya Dimitrova E: Todor Stamatov. Steliyan Georgiev8 Ves(R. Mira. Nikolay Yankovdo James Grahamino co mey to.loiceQ Describe what you are looking for€. Vasil Vasileve MessagerNoR0d canvis© Files& Pins +Пе не става, бях го направил така, и се счупиLukas Kovalik 3:10 PMами и при мен се чумешеVasil Vasilev 3:11 PMpredis полльожа set(Skey. Svalue. "'nx' - true. ex =60)DooywLukas Kovalik 3:11 PMVasil Vasilev 3:11 PMтака май не работеше Sresult - Redis::set(StestKey. StestValue, 'EX, Stti, 'NX): а така да Sresult - Redis:set(StestKey. StestValue, ['nx, 'ex' => $tti]*Lukas Kovalik 3:12PMNo aaune soudVasil Vasilev 3:12 PMобратното може да еtkac Kow.ik 2420Mможе и да брькамVasil Vasilev 3:12 PMT KONh ИMASlockAcquired - Redis::set(self::POLLING_LOCK,KEY, Json_encode(SlockData), *EX", self::LOCK_TTL_SECONDS, *NX*)значи пьовото работиLukas Kovalik 313 PMда и в арр/Services/Crm/Hubspot/Client.php:103Vasil Vasilev 3:14 PMLukas Kovalik 3:16 PMсега ги глелам, че и двете ми ги прави greved out полселните лва параметраVasil Vasilev 3:17 PMMusMoдано няма нещо промененоVasil Vasilev 3:17 PMне знам1Oe0...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85341
|
NULL
|
0
|
2026-05-28T12:18:58.377266+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970738377_m1.jpg...
|
Slack
|
Vasil Vasilev (DM) - Jiminny Inc - 2 new items - S Vasil Vasilev (DM) - Jiminny Inc - 2 new items - Slack...
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
Stoyan Tomov
Petko Kashinski
Galya Dimitrova
Todor Stamatov
Steliyan Georgiev
Ves
Mira
Nikolay Yankov
James Graham
Lukas Kovalik
you
Jira Cloud
Toast
Google Calendar
Messages
Messages
Add canvas
Add canvas
Files
Files
Pins
Pins
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
Lukas Kovalik
Today at 2:45:12 PM
2:45 PM
Не знам това дали е релевантно
Today at 2:45:14 PM
2:45
1.
Shared Elasticsearch Sockets:
Because
pcntl_fork()
clones file descriptors, both child processes will share the exact same HTTP/TCP socket for Elasticsearch. Since you didn’t reset the ES client in...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"platform-backend-engineers","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"bugs","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"donut_time","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"general","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"happy_birthday","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"infosec_internal_all","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"infra-changes","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"infrastructure_dev","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"jbu-team-info","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"platform-team","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"random","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"support","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Stefka Stoyanova","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Iliyana Netseva","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tomov","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Petko Kashinski","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Todor Stamatov","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Ves","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Mira","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"James Graham","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Lukas Kovalik","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"you","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Jira Cloud","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Toast","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Google Calendar","depth":15,"bounds":{"left":0.56805557,"top":0.0,"width":0.06388889,"height":0.02},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.56805557,"top":0.0,"width":0.007638889,"height":0.02}},{"char_start":1,"char_count":14,"bounds":{"left":0.57569444,"top":0.0,"width":0.06875,"height":0.02}}],"role_description":"text"},{"role":"AXRadioButton","text":"Messages","depth":9,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Messages","depth":11,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Add canvas","depth":10,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Add canvas","depth":12,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":9,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":11,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Pins","depth":9,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Pins","depth":11,"on_screen":true,"role_description":"text"},{"role":"AXPopUpButton","text":"Add and Edit Channel Tabs","depth":9,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Canvas","depth":9,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"List","depth":9,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":9,"on_screen":true,"role_description":"text"},{"role":"AXPopUpButton","text":"Jump to date","depth":15,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Lukas Kovalik","depth":16,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"Today at 2:45:12 PM","depth":16,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2:45 PM","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Не знам това дали е релевантно","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"Today at 2:45:14 PM","depth":17,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2:45","depth":18,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"1.","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Shared Elasticsearch Sockets:","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Because","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"pcntl_fork()","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"clones file descriptors, both child processes will share the exact same HTTP/TCP socket for Elasticsearch. Since you didn’t reset the ES client in","depth":16,"on_screen":false,"role_description":"text"}]...
|
6487283437127157380
|
-8136328129988029754
|
typing_pause
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
Stoyan Tomov
Petko Kashinski
Galya Dimitrova
Todor Stamatov
Steliyan Georgiev
Ves
Mira
Nikolay Yankov
James Graham
Lukas Kovalik
you
Jira Cloud
Toast
Google Calendar
Messages
Messages
Add canvas
Add canvas
Files
Files
Pins
Pins
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
Lukas Kovalik
Today at 2:45:12 PM
2:45 PM
Не знам това дали е релевантно
Today at 2:45:14 PM
2:45
1.
Shared Elasticsearch Sockets:
Because
pcntl_fork()
clones file descriptors, both child processes will share the exact same HTTP/TCP socket for Elasticsearch. Since you didn’t reset the ES client in
PhpStormFileEditViewNavigateCodeRefactorRunToolsGitWindowHelpec2-user@ip-10-30-129-190:~screenpipe"‹$0(ah]БГ100% <78• Thu 28 May 15:18:58181ec2-user@ip-10-30-140-….₴7DOCKER#_####_#####\\###||\#/V~'881DEV (-zsh)O [EMAIL] Linux 2023 (ECS Optimized)_/m/For documentation, visit [URL_WITH_CREDENTIALS] ~]$ exitlogoutConnection to jiminny-prod-ecsi closed.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ applukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ vprodWarning: Permanently added 'jiminny-prod-ecs1' (ED25519) to the list of known hosts.A newer release of "Amazon Linux" is available.Version 2023.10.20260330:Version 2023.11.20260406:Version 2023.11.20260413:Version2023.11.20260427:Version2023.11.20260505:Version 2023.11.20260509:Version 2023.11.20260511:Version 2023.11.20260514:Run "/usr/bin/dnf check-release-update" for full release and version update info#_~\ ####_\ #####\\###1\#/Amazon Linux 2023 (ECS Optimized)/m/'For documentation, visit [URL_WITH_CREDENTIALS] ~]$ client_loop: send disconnect: Broken pipeukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-relay) $I...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85340
|
NULL
|
0
|
2026-05-28T12:18:57.843015+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970737843_m2.jpg...
|
Slack
|
Vasil Vasilev (DM) - Jiminny Inc - 2 new items - S Vasil Vasilev (DM) - Jiminny Inc - 2 new items - Slack...
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
Stoyan Tomov
Petko Kashinski
Galya Dimitrova
Todor Stamatov
Steliyan Georgiev...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"bounds":{"left":0.5056516,"top":0.058260176,"width":0.011968086,"height":0.028731046},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"bounds":{"left":0.50299203,"top":0.10055866,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":8,"bounds":{"left":0.50664896,"top":0.13806863,"width":0.009973404,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":6,"bounds":{"left":0.50299203,"top":0.15482841,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":8,"bounds":{"left":0.50764626,"top":0.19233839,"width":0.007978723,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":6,"bounds":{"left":0.50299203,"top":0.20909816,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":8,"bounds":{"left":0.5053192,"top":0.24660814,"width":0.012965426,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5053192,"top":0.24660814,"width":0.0026595744,"height":0.011173184}},{"char_start":1,"char_count":7,"bounds":{"left":0.50764626,"top":0.24660814,"width":0.010638298,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":6,"bounds":{"left":0.50299203,"top":0.26336792,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":8,"bounds":{"left":0.50797874,"top":0.3008779,"width":0.0076462766,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50797874,"top":0.3008779,"width":0.0019946808,"height":0.011173184}},{"char_start":1,"char_count":4,"bounds":{"left":0.5099734,"top":0.3008779,"width":0.0056515955,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":6,"bounds":{"left":0.50299203,"top":0.31763768,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":8,"bounds":{"left":0.50731385,"top":0.35514766,"width":0.008643617,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50731385,"top":0.35514766,"width":0.0019946808,"height":0.011173184}},{"char_start":1,"char_count":4,"bounds":{"left":0.5093085,"top":0.35514766,"width":0.0066489363,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":6,"bounds":{"left":0.50299203,"top":0.3719074,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":8,"bounds":{"left":0.50731385,"top":0.4094174,"width":0.008976064,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50731385,"top":0.4094174,"width":0.0033244682,"height":0.011173184}},{"char_start":1,"char_count":3,"bounds":{"left":0.5106383,"top":0.4094174,"width":0.0056515955,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"platform-backend-engineers","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":15,"bounds":{"left":0.5422208,"top":0.09736632,"width":0.018284574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.09736632,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54521275,"top":0.09736632,"width":0.015292553,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"bugs","depth":15,"bounds":{"left":0.5422208,"top":0.11971269,"width":0.010638298,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.11971269,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":3,"bounds":{"left":0.54521275,"top":0.11971269,"width":0.00731383,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":15,"bounds":{"left":0.5422208,"top":0.14205906,"width":0.034574468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.14205906,"width":0.0023271276,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54454786,"top":0.14205906,"width":0.032247342,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"donut_time","depth":15,"bounds":{"left":0.5422208,"top":0.16440542,"width":0.025265958,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.16440542,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54521275,"top":0.16440542,"width":0.022273935,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":15,"bounds":{"left":0.5422208,"top":0.1867518,"width":0.025930852,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.1867518,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":10,"bounds":{"left":0.54488033,"top":0.1867518,"width":0.022938829,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"general","depth":15,"bounds":{"left":0.5422208,"top":0.20909816,"width":0.016289894,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.20909816,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54488033,"top":0.20909816,"width":0.013297873,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"happy_birthday","depth":15,"bounds":{"left":0.5422208,"top":0.23144454,"width":0.033909574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.23144454,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":13,"bounds":{"left":0.54521275,"top":0.23144454,"width":0.030917553,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"infosec_internal_all","depth":15,"bounds":{"left":0.5422208,"top":0.25379092,"width":0.041888297,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.25379092,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":19,"bounds":{"left":0.54355055,"top":0.25379092,"width":0.040226065,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"infra-changes","depth":15,"bounds":{"left":0.5422208,"top":0.27613726,"width":0.029587766,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.27613726,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54355055,"top":0.27613726,"width":0.02825798,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"infrastructure_dev","depth":15,"bounds":{"left":0.5422208,"top":0.29848364,"width":0.040226065,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.29848364,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":17,"bounds":{"left":0.54355055,"top":0.29848364,"width":0.038896278,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"jbu-team-info","depth":15,"bounds":{"left":0.5422208,"top":0.32083002,"width":0.029920213,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.32083002,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54355055,"top":0.32083002,"width":0.028590426,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":15,"bounds":{"left":0.5422208,"top":0.34317636,"width":0.023271276,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.34317636,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54355055,"top":0.34317636,"width":0.021609042,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"platform-team","depth":15,"bounds":{"left":0.5422208,"top":0.36552274,"width":0.03158245,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.36552274,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54521275,"top":0.36552274,"width":0.028590426,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":15,"bounds":{"left":0.5422208,"top":0.38786912,"width":0.034906916,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.38786912,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54521275,"top":0.38786912,"width":0.031914894,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":15,"bounds":{"left":0.5422208,"top":0.4102155,"width":0.03856383,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.4102155,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54521275,"top":0.4102155,"width":0.03557181,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"random","depth":15,"bounds":{"left":0.5422208,"top":0.43256184,"width":0.016954787,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.43256184,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":5,"bounds":{"left":0.54421544,"top":0.43256184,"width":0.014960106,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":15,"bounds":{"left":0.5422208,"top":0.45490822,"width":0.018284574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.45490822,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":7,"bounds":{"left":0.54421544,"top":0.45490822,"width":0.016289894,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"support","depth":15,"bounds":{"left":0.5422208,"top":0.4772546,"width":0.017287234,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.4772546,"width":0.0023271276,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54454786,"top":0.4772546,"width":0.01462766,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":15,"bounds":{"left":0.5422208,"top":0.49960095,"width":0.024601065,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.49960095,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54421544,"top":0.49960095,"width":0.022606382,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":15,"bounds":{"left":0.5422208,"top":0.5219473,"width":0.045212764,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.5219473,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":20,"bounds":{"left":0.54421544,"top":0.5219473,"width":0.04720745,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":15,"bounds":{"left":0.5422208,"top":0.5746209,"width":0.026595745,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.5746209,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54521275,"top":0.5746209,"width":0.023271276,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Stefka Stoyanova","depth":15,"bounds":{"left":0.5422208,"top":0.5969673,"width":0.037898935,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.5969673,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54488033,"top":0.5969673,"width":0.03523936,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Iliyana Netseva","depth":15,"bounds":{"left":0.5422208,"top":0.61931366,"width":0.03324468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.61931366,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":14,"bounds":{"left":0.54355055,"top":0.61931366,"width":0.031914894,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tomov","depth":15,"bounds":{"left":0.5422208,"top":0.64166003,"width":0.030917553,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.64166003,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":11,"bounds":{"left":0.54488033,"top":0.64166003,"width":0.027925532,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Petko Kashinski","depth":15,"bounds":{"left":0.5422208,"top":0.6640064,"width":0.034574468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.6640064,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":14,"bounds":{"left":0.54521275,"top":0.6640064,"width":0.03158245,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":15,"bounds":{"left":0.5422208,"top":0.6863527,"width":0.03523936,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.6863527,"width":0.003656915,"height":0.014365523}},{"char_start":1,"char_count":14,"bounds":{"left":0.54587764,"top":0.6863527,"width":0.03158245,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Todor Stamatov","depth":15,"bounds":{"left":0.5422208,"top":0.7086991,"width":0.034574468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.7086991,"width":0.0023271276,"height":0.014365523}},{"char_start":1,"char_count":13,"bounds":{"left":0.54454786,"top":0.7086991,"width":0.031914894,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":15,"bounds":{"left":0.5422208,"top":0.7310455,"width":0.038231384,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.7310455,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":16,"bounds":{"left":0.54488033,"top":0.7310455,"width":0.03557181,"height":0.014365523}}],"role_description":"text"}]...
|
1282717916426910583
|
-5801313977132400914
|
typing_pause
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
Stoyan Tomov
Petko Kashinski
Galya Dimitrova
Todor Stamatov
Steliyan Georgiev
SIUCKFV faVsco.is ~Projectv© VoiceConsentPrefix.phpen NoticationsnObservers© AccountObserver.php© ActivityObserver.php© ContactObserver.php© [EMAIL]© ProfileObserver.phpSocialAccountObserver.phpUserObserver.php© UserRoleObserver.php> Co PoliciesC ProvidersQueuea ReoositonieCRutesvserviceActivityAiReodrEJ AvatarTeallendarVIChRotonWindowHelp#12121 on JY-20963-fox-hService.phpphe helpers.phpDeleteObjectsTrait.phpOLenkоkyseiwico.on8 UserRoleObserverTest.phpC TextRelayServiceTest.phg(©) Hubspot.JournalPollinaService.ohp x$512Cass nuosoo vournalroco noseiv* Acquire exclusive polling lock to prevent aultiple instancesrivate function acquirePollingLock(): boolBullhorne copperimermobiacteDecorateActivityDummy› @ Helpersv im HibsodAccountSyncStrategyIhctiontContactSyncStrategy› DODTO333› E Fieldswhtaurnd© HubspotClientCredenti339@HubspotDealWebhook 340© HubspotJournalApiCie341© HubspotJournalPolling: 342© HubspotWebhookSubs© JournalApiResult.php© JournalßatchSizeLimitE© JournalEventTransforma MetadataOpportunitySyncStrategy→lmlpae natioo* Rerease the potting lockrivate function releasePollingLock(): voidf...,* Force release the polling lock (for emergency situations)ublic function forceReleaseLock(): voidf...* Signat the polling service to stop gracefullyublic function requestStop(): void{...}©ActivityController.php© Kernel.phpE customlogA console (STAGING)# laravel.logJiminnY ….A12X9 A V—782783704705786733712=713-74715- 716'EX', self::LOCK,71772072272723ABREED732733734735736737738TX: AUTONAND a.created_at > DATE_SGROUP BY u.id, v.email,ORDER BY sns_count DESCHommeselect x fron teans wherdDMsselect * fron roles:ActivitySELECTCONCAT(U.1d, CASE WHEsa.*,t.ouner_id FROM socieJOIN users u on u.id = stJOIN teans tWHERE v.tean_id = 1117 arSELECT * FROM activitiesSELECT * FROM activitiosSELECT * FROM crn_configuSELECT * FROM teans WHERHselleetron usens whenselect * fron playbooksselect * fron playbook_.ctselect * fron playbook_ctselect * fron crn_fieldsselect * fron crn_field.vSELECT * FROM Crn_field_c* JOIN crn_fields f ON fo• JOIN activities a ON $6WHERE activity_id = 7993:AND f.crn_provider id00MonSELECT * FROM activity_moselect * fron text_relaysselect * fron activitiesselect * fron users wherdselect x fron activities# backend# bugs# confusion-clinic# donut_time# engineering# general# happy_birthday8 infosec_internal_all# infra-changes# infrastructure_dev# jbu-team-info# jiminny-bg# platform-team# platform-ticketstoroduct launche# random# releases# support# thank-yous# the people of jimi..a? Direct messages€. Vasil Vasilev8. Stefka Stoyanova[. lliyana Netseva88. Stoyan Tomov® Petko KashinskiD, Galya Dimitrova E: Todor Stamatov. Steliyan Georgiev(R. Mira. Nikolay Yankovdo James Graham2. Lukas Kovalik y….ide copied to clipboardselect x fron teans wherdselect x fron activity_stselect x fron activity_st*:Apps6 Jira CloudTO0%7Ainu co moy to.loroQ Describe what you are looking for€. Vasil Vasileve MessagerNoR0d canvis© Files&2 Pins +или с друг driverVasil Vasilev 3:10 PMне става, бях го направил така, и се счупиLukas Kovalik 3:10 PMами и пои мен се чумешеVasil Vasilev 3.11 PMpredis поддьржа set(Skey, Svalue. ['nx' - true, ex -60))c onorcais нe cтaваLukas Kovalik 3.11PMVasil Vasilev 3:11 PMвторото пробвах, и не станаLukas Kovalik 3:12PMDooywтака май не работеше Sresult - Redis:set(StestKey, StestValue, 'EX, Stti, "NX*); а така да Sresult - Redis:set(StestKey, StestValue, ['nx, 'ex' »> $ttl]):не помня врuдWellaGlleu 220Vобратното може да еLukas Kovalik 3:12PMможе и да брькамVasil Vasilev 3:12 PMAKOna MMMockhcanren Pedeucntiea resiimtiky conencodelslockhotennx satlnknistiзначи пьрвото работиLukas Kovalik 3:13 PMда и в арр/Services/Crm/Hubspot/Client.php:103Vasil Vasilev 3:14 PMLukas Kovalik 3:16 PMсега ги гледам, че и двете ми ги прави greyed out полседните два параметраNacllNacllou 147ovMusMonLukas Kovalik 3:17PMдано няма нещо промененоVasil Vasilev 3:17 PMwe ch to ocrang kakyо eShift + Return to aid a now in...
|
85339
|
NULL
|
NULL
|
NULL
|
|
85339
|
2921
|
50
|
2026-05-28T12:18:54.137642+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970734137_m2.jpg...
|
Slack
|
Vasil Vasilev (DM) - Jiminny Inc - 2 new items - S Vasil Vasilev (DM) - Jiminny Inc - 2 new items - Slack...
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
Stoyan Tomov
Petko Kashinski
Galya Dimitrova
Todor Stamatov
Steliyan Georgiev
Ves
Mira
Nikolay Yankov
James Graham
Lukas Kovalik
you
Jira Cloud
Toast
Google Calendar
Messages
Messages
Add canvas
Add canvas
Files
Files
Pins
Pins
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
Today at 1:25:47 PM
1:25
процесите са отделни, за да не се борят за място под слънцето в един и също процес, да можем да им мерим поотделно кой колко памет яде, и съответно да нагласяме размера на всеки batch по тип
Today at 1:26:15 PM
1:26
и да не си пречат, когато има данни за индескиране в опашките и за activity и за opportunity...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"bounds":{"left":0.5056516,"top":0.058260176,"width":0.011968086,"height":0.028731046},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"bounds":{"left":0.50299203,"top":0.10055866,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":8,"bounds":{"left":0.50664896,"top":0.13806863,"width":0.009973404,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":6,"bounds":{"left":0.50299203,"top":0.15482841,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":8,"bounds":{"left":0.50764626,"top":0.19233839,"width":0.007978723,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":6,"bounds":{"left":0.50299203,"top":0.20909816,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":8,"bounds":{"left":0.5053192,"top":0.24660814,"width":0.012965426,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5053192,"top":0.24660814,"width":0.0026595744,"height":0.011173184}},{"char_start":1,"char_count":7,"bounds":{"left":0.50764626,"top":0.24660814,"width":0.010638298,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":6,"bounds":{"left":0.50299203,"top":0.26336792,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":8,"bounds":{"left":0.50797874,"top":0.3008779,"width":0.0076462766,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50797874,"top":0.3008779,"width":0.0019946808,"height":0.011173184}},{"char_start":1,"char_count":4,"bounds":{"left":0.5099734,"top":0.3008779,"width":0.0056515955,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":6,"bounds":{"left":0.50299203,"top":0.31763768,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":8,"bounds":{"left":0.50731385,"top":0.35514766,"width":0.008643617,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50731385,"top":0.35514766,"width":0.0019946808,"height":0.011173184}},{"char_start":1,"char_count":4,"bounds":{"left":0.5093085,"top":0.35514766,"width":0.0066489363,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":6,"bounds":{"left":0.50299203,"top":0.3719074,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":8,"bounds":{"left":0.50731385,"top":0.4094174,"width":0.008976064,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50731385,"top":0.4094174,"width":0.0033244682,"height":0.011173184}},{"char_start":1,"char_count":3,"bounds":{"left":0.5106383,"top":0.4094174,"width":0.0056515955,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"platform-backend-engineers","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":15,"bounds":{"left":0.5422208,"top":0.09736632,"width":0.018284574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.09736632,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54521275,"top":0.09736632,"width":0.015292553,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"bugs","depth":15,"bounds":{"left":0.5422208,"top":0.11971269,"width":0.010638298,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.11971269,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":3,"bounds":{"left":0.54521275,"top":0.11971269,"width":0.00731383,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":15,"bounds":{"left":0.5422208,"top":0.14205906,"width":0.034574468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.14205906,"width":0.0023271276,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54454786,"top":0.14205906,"width":0.032247342,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"donut_time","depth":15,"bounds":{"left":0.5422208,"top":0.16440542,"width":0.025265958,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.16440542,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54521275,"top":0.16440542,"width":0.022273935,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":15,"bounds":{"left":0.5422208,"top":0.1867518,"width":0.025930852,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.1867518,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":10,"bounds":{"left":0.54488033,"top":0.1867518,"width":0.022938829,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"general","depth":15,"bounds":{"left":0.5422208,"top":0.20909816,"width":0.016289894,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.20909816,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54488033,"top":0.20909816,"width":0.013297873,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"happy_birthday","depth":15,"bounds":{"left":0.5422208,"top":0.23144454,"width":0.033909574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.23144454,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":13,"bounds":{"left":0.54521275,"top":0.23144454,"width":0.030917553,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"infosec_internal_all","depth":15,"bounds":{"left":0.5422208,"top":0.25379092,"width":0.041888297,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.25379092,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":19,"bounds":{"left":0.54355055,"top":0.25379092,"width":0.040226065,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"infra-changes","depth":15,"bounds":{"left":0.5422208,"top":0.27613726,"width":0.029587766,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.27613726,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54355055,"top":0.27613726,"width":0.02825798,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"infrastructure_dev","depth":15,"bounds":{"left":0.5422208,"top":0.29848364,"width":0.040226065,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.29848364,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":17,"bounds":{"left":0.54355055,"top":0.29848364,"width":0.038896278,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"jbu-team-info","depth":15,"bounds":{"left":0.5422208,"top":0.32083002,"width":0.029920213,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.32083002,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54355055,"top":0.32083002,"width":0.028590426,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":15,"bounds":{"left":0.5422208,"top":0.34317636,"width":0.023271276,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.34317636,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54355055,"top":0.34317636,"width":0.021609042,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"platform-team","depth":15,"bounds":{"left":0.5422208,"top":0.36552274,"width":0.03158245,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.36552274,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54521275,"top":0.36552274,"width":0.028590426,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":15,"bounds":{"left":0.5422208,"top":0.38786912,"width":0.034906916,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.38786912,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54521275,"top":0.38786912,"width":0.031914894,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":15,"bounds":{"left":0.5422208,"top":0.4102155,"width":0.03856383,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.4102155,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54521275,"top":0.4102155,"width":0.03557181,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"random","depth":15,"bounds":{"left":0.5422208,"top":0.43256184,"width":0.016954787,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.43256184,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":5,"bounds":{"left":0.54421544,"top":0.43256184,"width":0.014960106,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":15,"bounds":{"left":0.5422208,"top":0.45490822,"width":0.018284574,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.45490822,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":7,"bounds":{"left":0.54421544,"top":0.45490822,"width":0.016289894,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"support","depth":15,"bounds":{"left":0.5422208,"top":0.4772546,"width":0.017287234,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.4772546,"width":0.0023271276,"height":0.014365523}},{"char_start":1,"char_count":6,"bounds":{"left":0.54454786,"top":0.4772546,"width":0.01462766,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":15,"bounds":{"left":0.5422208,"top":0.49960095,"width":0.024601065,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.49960095,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54421544,"top":0.49960095,"width":0.022606382,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":15,"bounds":{"left":0.5422208,"top":0.5219473,"width":0.045212764,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.5219473,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":20,"bounds":{"left":0.54421544,"top":0.5219473,"width":0.04720745,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":15,"bounds":{"left":0.5422208,"top":0.5746209,"width":0.026595745,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.5746209,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54521275,"top":0.5746209,"width":0.023271276,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Stefka Stoyanova","depth":15,"bounds":{"left":0.5422208,"top":0.5969673,"width":0.037898935,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.5969673,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.54488033,"top":0.5969673,"width":0.03523936,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Iliyana Netseva","depth":15,"bounds":{"left":0.5422208,"top":0.61931366,"width":0.03324468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.61931366,"width":0.0013297872,"height":0.014365523}},{"char_start":1,"char_count":14,"bounds":{"left":0.54355055,"top":0.61931366,"width":0.031914894,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tomov","depth":15,"bounds":{"left":0.5422208,"top":0.64166003,"width":0.030917553,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.64166003,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":11,"bounds":{"left":0.54488033,"top":0.64166003,"width":0.027925532,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Petko Kashinski","depth":15,"bounds":{"left":0.5422208,"top":0.6640064,"width":0.034574468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.6640064,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":14,"bounds":{"left":0.54521275,"top":0.6640064,"width":0.03158245,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":15,"bounds":{"left":0.5422208,"top":0.6863527,"width":0.03523936,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.6863527,"width":0.003656915,"height":0.014365523}},{"char_start":1,"char_count":14,"bounds":{"left":0.54587764,"top":0.6863527,"width":0.03158245,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Todor Stamatov","depth":15,"bounds":{"left":0.5422208,"top":0.7086991,"width":0.034574468,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.7086991,"width":0.0023271276,"height":0.014365523}},{"char_start":1,"char_count":13,"bounds":{"left":0.54454786,"top":0.7086991,"width":0.031914894,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":15,"bounds":{"left":0.5422208,"top":0.7310455,"width":0.038231384,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.7310455,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":16,"bounds":{"left":0.54488033,"top":0.7310455,"width":0.03557181,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Ves","depth":15,"bounds":{"left":0.5422208,"top":0.75339186,"width":0.007978723,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.75339186,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":2,"bounds":{"left":0.54521275,"top":0.75339186,"width":0.004986702,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Mira","depth":15,"bounds":{"left":0.5422208,"top":0.77573824,"width":0.010305851,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.77573824,"width":0.004654255,"height":0.014365523}},{"char_start":1,"char_count":3,"bounds":{"left":0.546875,"top":0.77573824,"width":0.005319149,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":15,"bounds":{"left":0.5422208,"top":0.7980846,"width":0.032912236,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.7980846,"width":0.0039893617,"height":0.014365523}},{"char_start":1,"char_count":13,"bounds":{"left":0.5462101,"top":0.7980846,"width":0.028922873,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"James Graham","depth":15,"bounds":{"left":0.5422208,"top":0.820431,"width":0.031914894,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.820431,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":11,"bounds":{"left":0.54421544,"top":0.820431,"width":0.029920213,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Lukas Kovalik","depth":15,"bounds":{"left":0.5422208,"top":0.8427773,"width":0.029587766,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.8427773,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.54488033,"top":0.8427773,"width":0.026928192,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"you","depth":15,"bounds":{"left":0.5744681,"top":0.8427773,"width":0.0063164895,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5744681,"top":0.8427773,"width":0.0023271276,"height":0.014365523}},{"char_start":1,"char_count":2,"bounds":{"left":0.5767952,"top":0.8427773,"width":0.0056515955,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Jira Cloud","depth":15,"bounds":{"left":0.5422208,"top":0.8954509,"width":0.021941489,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.8954509,"width":0.0019946808,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.54421544,"top":0.8954509,"width":0.019946808,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Toast","depth":15,"bounds":{"left":0.5422208,"top":0.91779727,"width":0.011635638,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.91779727,"width":0.0023271276,"height":0.014365523}},{"char_start":1,"char_count":4,"bounds":{"left":0.54454786,"top":0.91779727,"width":0.00930851,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"Google Calendar","depth":15,"bounds":{"left":0.5422208,"top":0.94014364,"width":0.030585106,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5422208,"top":0.94014364,"width":0.003656915,"height":0.014365523}},{"char_start":1,"char_count":14,"bounds":{"left":0.54587764,"top":0.94014364,"width":0.032912236,"height":0.014365523}}],"role_description":"text"},{"role":"AXRadioButton","text":"Messages","depth":9,"bounds":{"left":0.60206115,"top":0.09177973,"width":0.030917553,"height":0.030327214},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Messages","depth":11,"bounds":{"left":0.61136967,"top":0.10055866,"width":0.018949468,"height":0.012769354},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.61136967,"top":0.10055866,"width":0.0039893617,"height":0.012769354}},{"char_start":1,"char_count":7,"bounds":{"left":0.61535907,"top":0.10055866,"width":0.014960106,"height":0.012769354}}],"role_description":"text"},{"role":"AXRadioButton","text":"Add canvas","depth":10,"bounds":{"left":0.63397604,"top":0.09177973,"width":0.034242023,"height":0.030327214},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Add canvas","depth":12,"bounds":{"left":0.64328456,"top":0.10055866,"width":0.022273935,"height":0.012769354},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.64328456,"top":0.10055866,"width":0.0033244682,"height":0.012769354}},{"char_start":1,"char_count":9,"bounds":{"left":0.6462766,"top":0.10055866,"width":0.019281914,"height":0.012769354}}],"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":9,"bounds":{"left":0.66921544,"top":0.09177973,"width":0.020944148,"height":0.030327214},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":11,"bounds":{"left":0.67852396,"top":0.10055866,"width":0.008976064,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Pins","depth":9,"bounds":{"left":0.69148934,"top":0.09177973,"width":0.020279255,"height":0.030327214},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Pins","depth":11,"bounds":{"left":0.70079786,"top":0.10055866,"width":0.00831117,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXPopUpButton","text":"Add and Edit Channel Tabs","depth":9,"bounds":{"left":0.71276593,"top":0.09177973,"width":0.010970744,"height":0.030327214},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Canvas","depth":9,"bounds":{"left":0.59640956,"top":0.0518755,"width":0.015957447,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"List","depth":9,"bounds":{"left":0.59640956,"top":0.0518755,"width":0.007978723,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":9,"bounds":{"left":0.59640956,"top":0.0518755,"width":0.013962766,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXPopUpButton","text":"Jump to date","depth":14,"bounds":{"left":0.7849069,"top":0.12689546,"width":0.025265958,"height":0.022346368},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Today at 1:25:47 PM","depth":16,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1:25","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"процесите са отделни, за да не се борят за място под слънцето в един и също процес, да можем да им мерим поотделно кой колко памет яде, и съответно да нагласяме размера на всеки batch по тип","depth":16,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"Today at 1:26:15 PM","depth":16,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1:26","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"и да не си пречат, когато има данни за индескиране в опашките и за activity и за opportunity","depth":16,"on_screen":false,"role_description":"text"}]...
|
-2759124443109379399
|
-6188523760620694838
|
typing_pause
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
Stoyan Tomov
Petko Kashinski
Galya Dimitrova
Todor Stamatov
Steliyan Georgiev
Ves
Mira
Nikolay Yankov
James Graham
Lukas Kovalik
you
Jira Cloud
Toast
Google Calendar
Messages
Messages
Add canvas
Add canvas
Files
Files
Pins
Pins
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
Today at 1:25:47 PM
1:25
процесите са отделни, за да не се борят за място под слънцето в един и също процес, да можем да им мерим поотделно кой колко памет яде, и съответно да нагласяме размера на всеки batch по тип
Today at 1:26:15 PM
1:26
и да не си пречат, когато има данни за индескиране в опашките и за activity и за opportunity
SIUCKFV faVsco.is ~Projectv© VoiceConsentPrefix.phpen NoticationsnObservers© AccountObserver.php© ActivityObserver.php© ContactObserver.php© [EMAIL]© ProfileObserver.phpSocialAccountObserver.phpUserObserver.php© UserRoleObserver.php> Co PoliciesC ProvidersQueuea ReoositonieCRutesvserviceActivityAiReodrEJ AvatarlealendarVIChRotonWindowHelp#12121 on JY-20963-fox-hService.phppho helpers.phpDeleteObjectsTrait.phpOLeAkоkyScrco.on8 UserRoleObserverTest.phpC TextRelayServiceTest.phg(©) Hubspot.JournalPollinaService.ohp x$512Cass nuosoo vournalroco noseiv* Acquire exclusive polling lock to prevent aultiple instancesrivate function acquirePollingLock(): boolBullhorne copperimermobiacteDecorateActivityDummy› @ Helpersv im HibsodAccountSyncStrategyMAetioneContactSyncStrategy› DODTO331333› E Fieldswn taueea© HubspotClientCredenti339@HubspotDealWebhook 340© HubspotJournalApiCie341© HubspotJournalPolling: 342© HubspotWebhookSubs© JournalApiResult.php© JournalßatchSizeLimitE© JournalEventTransforma MetadataOpportunitySyncStrategy→lmlpae natioo* Rerease the potting lockrivate function releasePollingLock(): voidf...,* Force release the polling lock (for emergency situations)ublic function forceReleaseLock(): voidf...* Signat the polling service to stop gracefullyublic function requestStop(): void{...}©ActivityController.php© Kernel.phpE custom.logA console (STAGING)# laravel.logA12X9 A V—782783704705786733712=713-74715- 716'EX', self::LOCK,717720722722723ABREED732733734735736737738TX: AUTONAND a.created_at > DATE_SGROUP BY u.id, v.email,ORDER BY sns_count DESCselect x fron teans wherdselect * fron roles:SELECTCONCAT(U.1d, CASE WHEsa.*,t.ouner_id FROM socieJOIN users u on u.id = stJOIN teans tWHERE v.tean_id = 1117 arSELECT * FROM activitiesSELECT * FROM activitiosSELECT * FROM crn_configuSELECT * FROM teans WHERHselee ron usens whenselect * fron playbooksselect * fron playbook_caselect * fron playbook_ctselect * fron crn_fieldsselect * fron crn_field.vSELECT * FROM Crn_field_c* JOIN crn_fields f ON fo• JOIN activities a ON $6WHERE activity_id = 7993:AND f.crn_provider idSELECT * FROM activity_moselect * fron text_relaysselect * fron activitiesselect * fron users wherdselect x fron activitiesselect x fron accounts wiselect * fron users wherdselect x fron teans wherdselect x fron activity_stselect x fron activity_stHommeDMsActivity00MonJiminnY ….# backend# bugs# confusion-clinic# donut_time# engineering# general# happy_birthday8 infosec_internal_all# infra-changes# infrastructure_dev# jbu-team-info#jiminny-bg# platform-team# platform-ticketstoroduct launche# random# releases# support# thank-yous# the people of jimi..a? Direct messages€. Vasil Vasilev8. Stefka Stoyanova[. lliyana Netseva88. Stoyan Tomov® Petko KashinskiD, Galya Dimitrova E: Todor Stamatov. Steliyan Georgiev8 Ves(R. Mira. Nikolay Yankovdo James Graham2. Lukas Kovalik y….JO0%L7Ainu co woy 1o:lo.osQ Describe what you are looking for€. Vasil Vasileve MessagerNoR0d canvisO Files&2 Pins +или с друг driverVasil Vasilev 3:10 PMHe Caa.nay co MannaRun Takа, м Go CuvnиLukas Kovalik 3:10 PMами и пои мен се чумешеVasil Vasilev 3.11 PMpredis поддьржа set(Skey, Svalue. ['nx' - true, ex -60))c onorcais нe cтaваLukas Kovalik 3.11PMVasil Vasilev 3:11 PMвторото пробвах, и не станаLukas Kovalik 3:12PMDooywтака май не работеше Sresult - Redis:set(StestKey, StestValue, 'EX, Stti, "NX*); а така да Sresult - Redis:set(StestKey, StestValue, ['nx, 'ex' »> $ttl]):не помня врuдWellaGlleu 220Vобратното може да еLukas Kovalik 3:12PMможе и да брькамVasil Vasilev 3:12 PMAKOna MMMockhcanren Pedeucntiea resiimtiky conencodelslockhotennx satlnknistiзначи пьрвото работиLukas Kovalik 3:13 PMда и в арр/Services/Crm/Hubspot/Client.php:103Vasil Vasilev 3:14 PMLukas Kovalik 3:16 PMсега ги гледам, че и двете ми ги прави greyed out полседните два параметраNacllNacllou 147ovMusMonLukas Kovalik 3:17PMдано няма нещо промененоVasil Vasilev 3:17 PMwe ch to ocrang kakyо eShift + Return to add a new in...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85338
|
2920
|
47
|
2026-05-28T12:18:54.036152+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970734036_m1.jpg...
|
Slack
|
Vasil Vasilev (DM) - Jiminny Inc - 2 new items - S Vasil Vasilev (DM) - Jiminny Inc - 2 new items - Slack...
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
Stoyan Tomov
Petko Kashinski
Galya Dimitrova
Todor Stamatov
Steliyan Georgiev
Ves
Mira
Nikolay Yankov
James Graham
Lukas Kovalik
you
Jira Cloud
Toast
Google Calendar
Messages
Messages...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"platform-backend-engineers","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"bugs","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"donut_time","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"general","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"happy_birthday","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"infosec_internal_all","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"infra-changes","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"infrastructure_dev","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"jbu-team-info","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"platform-team","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"random","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"support","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Stefka Stoyanova","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Iliyana Netseva","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tomov","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Petko Kashinski","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Todor Stamatov","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Ves","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Mira","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"James Graham","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Lukas Kovalik","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"you","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Jira Cloud","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Toast","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Google Calendar","depth":15,"bounds":{"left":0.56805557,"top":0.0,"width":0.06388889,"height":0.02},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.56805557,"top":0.0,"width":0.007638889,"height":0.02}},{"char_start":1,"char_count":14,"bounds":{"left":0.57569444,"top":0.0,"width":0.06875,"height":0.02}}],"role_description":"text"},{"role":"AXRadioButton","text":"Messages","depth":9,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Messages","depth":11,"on_screen":true,"role_description":"text"}]...
|
2472280325389673958
|
-5802369499738826006
|
typing_pause
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
Stoyan Tomov
Petko Kashinski
Galya Dimitrova
Todor Stamatov
Steliyan Georgiev
Ves
Mira
Nikolay Yankov
James Graham
Lukas Kovalik
you
Jira Cloud
Toast
Google Calendar
Messages
Messages
PhpStormFileEditViewNavigateCodeRefactorRunToolsGitWindowHelpec2-user@ip-10-30-129-190:~screenpipe"84‹$0(ah]БГ100% <78• Thu 28 May 15:18:53181ec2-user@ip-10-30-140-….₴7DOCKER#_####_#####\\###||\#/V~'881DEV (-zsh)O [EMAIL] Linux 2023 (ECS Optimized)_/m/For documentation, visit [URL_WITH_CREDENTIALS] ~]$ exitlogoutConnection to jiminny-prod-ecsi closed.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ applukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ vprodWarning: Permanently added 'jiminny-prod-ecs1' (ED25519) to the list of known hosts.A newer release of "Amazon Linux" is available.Version 2023.10.20260330:Version 2023.11.20260406:Version 2023.11.20260413:Version2023.11.20260427:Version2023.11.20260505:Version 2023.11.20260509:Version 2023.11.20260511:Version 2023.11.20260514:Run "/usr/bin/dnf check-release-update" for full release and version update info#_~\ ####_\ #####\\###1\#/Amazon Linux 2023 (ECS Optimized)/m/'For documentation, visit [URL_WITH_CREDENTIALS] ~]$ client_loop: send disconnect: Broken pipeukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-relay) $I...
|
85336
|
NULL
|
NULL
|
NULL
|
|
85337
|
2921
|
49
|
2026-05-28T12:18:50.195878+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970730195_m2.jpg...
|
Slack
|
Vasil Vasilev (DM) - Jiminny Inc - 2 new items - S Vasil Vasilev (DM) - Jiminny Inc - 2 new items - Slack...
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"bounds":{"left":0.5056516,"top":0.058260176,"width":0.011968086,"height":0.028731046},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"bounds":{"left":0.50299203,"top":0.10055866,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":8,"bounds":{"left":0.50664896,"top":0.13806863,"width":0.009973404,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":6,"bounds":{"left":0.50299203,"top":0.15482841,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":8,"bounds":{"left":0.50764626,"top":0.19233839,"width":0.007978723,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":6,"bounds":{"left":0.50299203,"top":0.20909816,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":8,"bounds":{"left":0.5053192,"top":0.24660814,"width":0.012965426,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5053192,"top":0.24660814,"width":0.0026595744,"height":0.011173184}},{"char_start":1,"char_count":7,"bounds":{"left":0.50764626,"top":0.24660814,"width":0.010638298,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":6,"bounds":{"left":0.50299203,"top":0.26336792,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":8,"bounds":{"left":0.50797874,"top":0.3008779,"width":0.0076462766,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50797874,"top":0.3008779,"width":0.0019946808,"height":0.011173184}},{"char_start":1,"char_count":4,"bounds":{"left":0.5099734,"top":0.3008779,"width":0.0056515955,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":6,"bounds":{"left":0.50299203,"top":0.31763768,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":8,"bounds":{"left":0.50731385,"top":0.35514766,"width":0.008643617,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50731385,"top":0.35514766,"width":0.0019946808,"height":0.011173184}},{"char_start":1,"char_count":4,"bounds":{"left":0.5093085,"top":0.35514766,"width":0.0066489363,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":6,"bounds":{"left":0.50299203,"top":0.3719074,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":8,"bounds":{"left":0.50731385,"top":0.4094174,"width":0.008976064,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50731385,"top":0.4094174,"width":0.0033244682,"height":0.011173184}},{"char_start":1,"char_count":3,"bounds":{"left":0.5106383,"top":0.4094174,"width":0.0056515955,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"platform-backend-engineers","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":15,"on_screen":false,"role_description":"text"}]...
|
-177824806450888117
|
-8136785577719153025
|
typing_pause
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
SIUCKFV faVsco.is ~Projectv© VoiceConsentPrefix.phpen NoticationsnObservers© AccountObserver.php© ActivityObserver.php© ContactObserver.php© [EMAIL]© ProfileObserver.phpSocialAccountObserver.phpUserObserver.php© UserRoleObserver.php> Co PoliciesC ProvidersQueuea ReoositonieCRutesvserviceActivityAiReodrEJ AvatarlealendarVIChRotonWindowHelp#12121 on JY-20963-fox-hService.phpphe helpers.phpDeleteObjectsTrait.phpOLenkоkyseiwico.on8 UserRoleObserverTest.phpC TextRelayServiceTest.phg(©) Hubspot.JournalPollinaService.ohp x$512Cass nuosoo vournalroco noseiv* Acquire exclusive polling lock to prevent aultiple instancesrivate function acquirePollingLock(): boolBullhorne copperimermobiacteDecorateActivityDummy› @ Helpersv im HibsodAccountSyncStrategyIhctiontContactSyncStrategy› DODTO331333› E Fieldswn taueea© HubspotClientCredenti339@HubspotDealWebhook 340© HubspotJournalApiCie341© HubspotJournalPolling: 342© HubspotWebhookSubs© JournalApiResult.php© JournalßatchSizeLimitE© JournalEventTransforma MetadataOpportunitySyncStrategy→lmlpae natioo* Rerease the potting lockrivate function releasePollingLock(): voidf...,* Force release the polling lock (for emergency situations)ublic function forceReleaseLock(): voidf...* Signat the polling service to stop gracefullyublic function requestStop(): void{...}©ActivityController.php© Kernel.phpE customlogA console (STAGING)# laravel.logA12X9 A V—782783704705786733712=213-74715- 716'EX', self::LOCK,71772072272723ABREED732735734735736737738TX: AUTONAND a.created_at > DATE_SGROUP BY u.id, v.email,ORDER BY sns_count DESCselect x fron teans wherdselect * fron roles:SELECTCONCAT(U.1d, CASE WHEsa.*,t.ouner_id FROM socieJOIN users u on u.id = stJOIN teans tWHERE v.tean_id = 1117 arSELECT * FROM activitiesSELECT * FROM activitiosSELECT * FROM crn_configuSELECT * FROM teans WHERHselleetron usens whenselect * fron playbooksselect * fron playbook_.ctselect * fron playbook_ctselect * fron crn_fieldsselect * fron crn_field.vSELECT * FROM Crn_field_c* JOIN crn_fields f ON fo• JOIN activities a ON $6WHERE activity_id = 7993:AND f.crn_provider idSELECT * FROM activity_moselect * fron text_relaysselect * fron activitiesselect * fron users wherdselect x fron activitiesselect x fron accounts wiselect x fron teans wherdselect x fron activity_stselect x fron activity_stHommeDMsActivity00LaterMonJiminnY ….# backend# bugs# confusion-clinic# donut_time# engineering# general# happy_birthday8 infosec_internal_all# infra-changes# infrastructure_dev# jbu-team-info# jiminny-bg# platform-team# platform-ticketstoroduct launche# random# releases# support# thank-yous# the people of jimi..a? Direct messages€. Vasil Vasilev8. Stefka Stoyanova[. lliyana Netseva88. Stoyan Tomov® Petko KashinskiD, Galya Dimitrova E: Todor Stamatov. Steliyan Georgiev(R. Mira. Nikolay Yankovdo James Graham2. Lukas Kovalik y….Apps6 Jira CloudJO0%L7AInu cowoy 1o:lo.4Q Describe what you are looking for€. Vasil Vasileve MessagerNoR0d canvis© Files&2 Pins +или с друг driverVasil Vasilev 3:10 PMне става, бях го направил така, и се счупиLukas Kovalik 3:10 PMами и пои мен се чумешеVasil Vasilev 3.11 PMpredis поддьржа set(Skey, Svalue. ['nx' - true, ex -60))c onorcais нe cтaваLukas Kovalik 3.11PMVasil Vasilev 3:11 PMвторото пробвах, и не станаLukas Kovalik 3:12PMDooywтака май не работеше Sresult - Redis:set(StestKey, StestValue, 'EX, Stti, "NX*); а така да Sresult - Redis:set(StestKey, StestValue, ['nx, 'ex' »> $ttl]):не помня врuдWellaGlleu 220Vобратното може да еLukas Kovalik 3:12PMможе и да брькамVasil Vasilev 3:12 PMAKOna MMMockhcanren Pedeucntiea resiimtiky conencodelslockhotennx satlnknistiзначи пьрвото работиLukas Kovalik 3:13 PMда и в арр/Services/Crm/Hubspot/Client.php:103Vasil Vasilev 3:14 PMLukas Kovalik 3:16 PMсега ги гледам, че и двете ми ги прави greyed out полседните два параметраNacllNacllou 147ovMusMonLukas Kovalik 3:17PMдано няма нещо промененоVasil Vasilev 3:17 PMwe ch to ocrang kakyо eoк, мисля чеdShift + Return to aid a now in...
|
85334
|
NULL
|
NULL
|
NULL
|
|
85336
|
2920
|
46
|
2026-05-28T12:18:50.091261+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970730091_m1.jpg...
|
Slack
|
Vasil Vasilev (DM) - Jiminny Inc - 2 new items - S Vasil Vasilev (DM) - Jiminny Inc - 2 new items - Slack...
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"platform-backend-engineers","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"bugs","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"donut_time","depth":15,"on_screen":true,"role_description":"text"}]...
|
6801348342327917455
|
-5774503434061136129
|
typing_pause
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
PhpStormFileEditViewNavigateCodeRefactorRunToolsGitWindowHelpec2-user@ip-10-30-129-190:~ffmpeg84‹$0(ah]БГ100% <78• Thu 28 May 15:18:49181ec2-user@ip-10-30-140-….₴7DOCKER#_####_#####\\###||\#/V~'881DEV (-zsh)O [EMAIL] Linux 2023 (ECS Optimized)_/m/For documentation, visit [URL_WITH_CREDENTIALS] ~]$ exitlogoutConnection to jiminny-prod-ecsi closed.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ applukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ vprodWarning: Permanently added 'jiminny-prod-ecs1' (ED25519) to the list of known hosts.A newer release of "Amazon Linux" is available.Version 2023.10.20260330:Version 2023.11.20260406:Version 2023.11.20260413:Version2023.11.20260427:Version2023.11.20260505:Version 2023.11.20260509:Version 2023.11.20260511:Version 2023.11.20260514:Run "/usr/bin/dnf check-release-update" for full release and version update info#_~\ ####_\ #####\\###1\#/Amazon Linux 2023 (ECS Optimized)/m/'For documentation, visit [URL_WITH_CREDENTIALS] ~]$ client_loop: send disconnect: Broken pipeukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-relay) $I...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85335
|
2920
|
45
|
2026-05-28T12:18:48.540378+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970728540_m1.jpg...
|
Slack
|
Vasil Vasilev (DM) - Jiminny Inc - 2 new items - S Vasil Vasilev (DM) - Jiminny Inc - 2 new items - Slack...
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"platform-backend-engineers","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"bugs","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"donut_time","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"general","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"happy_birthday","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"infosec_internal_all","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"infra-changes","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"infrastructure_dev","depth":15,"on_screen":true,"role_description":"text"}]...
|
6106892229384445057
|
-5767818128486351105
|
typing_pause
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
PhpStormFileEditViewNavigateCodeRefactorRunToolsGitWindowHelpec2-user@ip-10-30-129-190:~ffmpeg‹$0(ah]БГ100% <78• Thu 28 May 15:18:48181ec2-user@ip-10-30-140-….₴7DOCKER#_####_#####\\###||\#/V~'881DEV (-zsh)O [EMAIL] Linux 2023 (ECS Optimized)_/m/For documentation, visit [URL_WITH_CREDENTIALS] ~]$ exitlogoutConnection to jiminny-prod-ecsi closed.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ applukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ vprodWarning: Permanently added 'jiminny-prod-ecs1' (ED25519) to the list of known hosts.A newer release of "Amazon Linux" is available.Version 2023.10.20260330:Version 2023.11.20260406:Version 2023.11.20260413:Version2023.11.20260427:Version2023.11.20260505:Version 2023.11.20260509:Version 2023.11.20260511:Version 2023.11.20260514:Run "/usr/bin/dnf check-release-update" for full release and version update info#_~\ ####_\ #####\\###1\#/Amazon Linux 2023 (ECS Optimized)/m/'For documentation, visit [URL_WITH_CREDENTIALS] ~]$ client_loop: send disconnect: Broken pipeukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-relay) $I...
|
85333
|
NULL
|
NULL
|
NULL
|
|
85334
|
2921
|
48
|
2026-05-28T12:18:46.367071+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970726367_m2.jpg...
|
Slack
|
Vasil Vasilev (DM) - Jiminny Inc - 2 new items - S Vasil Vasilev (DM) - Jiminny Inc - 2 new items - Slack...
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"bounds":{"left":0.5056516,"top":0.058260176,"width":0.011968086,"height":0.028731046},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"bounds":{"left":0.50299203,"top":0.10055866,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false}]...
|
-1719202967344989226
|
-8139130267114587598
|
typing_pause
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
SIUCKFV faVsco.is ~Projectv© VoiceConsentPrefix.phpen NoticationsnObservers© AccountObserver.php© ActivityObserver.php© ContactObserver.php© [EMAIL]© ProfileObserver.phpSocialAccountObserver.phpUserObserver.php© UserRoleObserver.php> Co PoliciesC ProvidersQueuea ReoositonieCRutesvserviceActivityAiReodrEJ AvatarlealendarVIChRotonWindowHelp#12121 on JY-20963-fox-hService.phppho helpers.phpDeleteObjectsTrait.phpOLenkоkyseiwico.on8 UserRoleObserverTest.phpC TextRelayServiceTest.phg(©) Hubspot.JournalPollinaService.ohp x$512Cass nuosoo vournalroco noseiv* Acquire exclusive polling lock to prevent aultiple instancesrivate function acquirePollingLock(): boolBullhorne copperimermobiacteDecorateActivityDummy› @ Helpersv im HibsodAccountSyncStrategyMAetioneContactSyncStrategy› DODTO331333› E Fieldswn taueea© HubspotClientCredenti339@HubspotDealWebhook 340© HubspotJournalApiCie341© HubspotJournalPolling: 342© HubspotWebhookSubs© JournalApiResult.php© JournalßatchSizeLimitE© JournalEventTransforma MetadataOpportunitySyncStrategy→lmlpae natioo* Rerease the potting lockrivate function releasePollingLock(): voidf...,* Force release the polling lock (for emergency situations)ublic function forceReleaseLock(): voidf...* Signat the polling service to stop gracefullyublic function requestStop(): void{...}©ActivityController.php© Kernel.phpE customlogA console (STAGING)# laravel.logA12X9 A V—782783704705786733712=213-74715- 716'EX', self::LOCK,71772072272723ABREED734735736737738TX: AUTONAND a.created_at > DATE_SGROUP BY u.id, v.email,ORDER BY sns_count DESCselect * fron teans wherdselect * fron roles:SELECTCONCAT(U.1d, CASE WHEsa.*,t.ouner_id FROM socieJOIN users u on u.id = stJOIN teans tWHERE v.tean_id = 1117 arSELECT * FROM activitiesSELECT * FROM activitiosSELECT * FROM crn_configuSELECT * FROM teans WHERHselleetron usens whenselect * fron playbooksselect * fron playbook_.ctselect * fron playbook_ctselect * fron crn_fieldsselect * fron crn_field.vSELECT * FROM Crn_field_c* JOIN crn_fields f ON fo• JOIN activities a ON $6WHERE activity_id = 7993:AND f.crn_provider idSELECT * FROM activity_moselect * fron text_relaysselect * fron activitiesselect * fron users wherdselect x fron activitiesselect * fron accounts wiselect x fron users wherdselect x fron teans wherdselect x fron activity_stselect x fron activity_stHommeDMsActivity00LaterMonJiminnY ….# backend# bugs# confusion-clinic# donut_time# engineering# general# happy_birthday8 infosec_internal_all# infra-changes# infrastructure_dev# jbu-team-info# jiminny-bg# platform-team# platform-ticketstoroduct launche# random# releases# support# thank-yous# the people of jimi..a? Direct messages€. Vasil Vasilev8. Stefka Stoyanova[. lliyana Netseva88. Stoyan Tomov® Petko KashinskiD, Galya Dimitrova E: Todor Stamatov. Steliyan Georgiev(R. Mira. Nikolay Yankovdo James Graham2. Lukas Kovalik y….Apps6 Jira CloudJO0%L7AInu cowoy 10:10.4Q Describe what you are looking for€. Vasil Vasileve MessagerNoR0d canvis© Files&2 Pins +или с друг driverVasil Vasilev 3:10 PMне става, бях го направил така, и се счупиLukas Kovalik 3:10 PMами и пои мен се чумешеVasil Vasilev 3.11 PMpredis поддьржа set(Skey, Svalue. ['nx' - true, ex -60))c onorcais нe cтaваLukas Kovalik 3.11PMVasil Vasilev 3:11 PMвторото пробвах, и не станаLukas Kovalik 3:12PMDooywтака май не работеше Sresult - Redis:set(StestKey, StestValue, 'EX, Stti, "NX*); а така да Sresult - Redis:set(StestKey, StestValue, ['nx, 'ex' »> $ttl]):не помня врuдWellaGlleu 220Vобратното може да еLukas Kovalik 3:12PMможе и да брькамVasil Vasilev 3:12 PMAKOna MMMockhcanren Pedeucntiea resiimtiky conencodelslockhotennx satlnknistiзначи пьрвото работиLukas Kovalik 3:13 PMда и в арр/Services/Crm/Hubspot/Client.php:103Vasil Vasilev 3:14 PMLukas Kovalik 3:16 PMсега ги гледам, че и двете ми ги прави greyed out полседните два параметраNacllNacllou 147ovMusMonLukas Kovalik 3:17PMдано няма нещо промененоVasil Vasilev 3:17 PMwe ch to ocrang kakyо e...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85333
|
2920
|
44
|
2026-05-28T12:18:46.266899+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970726266_m1.jpg...
|
Slack
|
Vasil Vasilev (DM) - Jiminny Inc - 2 new items - S Vasil Vasilev (DM) - Jiminny Inc - 2 new items - Slack...
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-7728254436789182889
|
1084668380251765430
|
typing_pause
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
PhpStormFileEditViewNavigateCodeRefactorRunToolsGitWindowHelpec2-user@ip-10-30-129-190:~ffmpeg84‹$0(ah]100% <78• Thu 28 May 15:18:45181ec2-user@ip-10-30-140-…₴7DOCKER#_####_#####\\###||\#/V~'881DEV (-zsh)O [EMAIL] Linux 2023 (ECS Optimized)_/m/For documentation, visit [URL_WITH_CREDENTIALS] ~]$ exitlogoutConnection to jiminny-prod-ecsi closed.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ applukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ vprodWarning: Permanently added 'jiminny-prod-ecs1' (ED25519) to the list of known hosts.A newer release of "Amazon Linux" is available.Version 2023.10.20260330:Version 2023.11.20260406:Version 2023.11.20260413:Version2023.11.20260427:Version2023.11.20260505:Version 2023.11.20260509:Version 2023.11.20260511:Version 2023.11.20260514:Run "/usr/bin/dnf check-release-update" for full release and version update info#_~\ ####_\ #####\\###1\#/Amazon Linux 2023 (ECS Optimized)/m/'For documentation, visit [URL_WITH_CREDENTIALS] ~]$ client_loop: send disconnect: Broken pipeukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-relay) $I...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85332
|
2920
|
43
|
2026-05-28T12:18:43.486333+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970723486_m1.jpg...
|
Slack
|
Vasil Vasilev (DM) - Jiminny Inc - 2 new items - S Vasil Vasilev (DM) - Jiminny Inc - 2 new items - Slack...
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":6,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":8,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":13,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"platform-backend-engineers","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"bugs","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"donut_time","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"general","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"happy_birthday","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"infosec_internal_all","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"infra-changes","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"infrastructure_dev","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"jbu-team-info","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"platform-team","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"random","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"support","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Stefka Stoyanova","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Iliyana Netseva","depth":15,"on_screen":true,"role_description":"text"}]...
|
5707154352875543036
|
-8071409655713906970
|
click
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
platform-backend-engineers
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
donut_time
engineering
general
happy_birthday
infosec_internal_all
infra-changes
infrastructure_dev
jbu-team-info
jiminny-bg
platform-team
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Vasil Vasilev
Stefka Stoyanova
Iliyana Netseva
PhpStormFileEditViewNavigateCodeRefactorRunToolsGitWindowHelpec2-user@ip-10-30-129-190:~ffmpeg84‹$0(ah]100% <78• Thu 28 May 15:18:43181ec2-user@ip-10-30-140-…₴7DOCKER#_####_#####\\###||\#/V~'881DEV (-zsh)O [EMAIL] Linux 2023 (ECS Optimized)_/m/For documentation, visit [URL_WITH_CREDENTIALS] ~]$ exitlogoutConnection to jiminny-prod-ecsi closed.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ applukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ vprodWarning: Permanently added 'jiminny-prod-ecs1' (ED25519) to the list of known hosts.A newer release of "Amazon Linux" is available.Version 2023.10.20260330:Version 2023.11.20260406:Version 2023.11.20260413:Version2023.11.20260427:Version2023.11.20260505:Version 2023.11.20260509:Version 2023.11.20260511:Version 2023.11.20260514:Run "/usr/bin/dnf check-release-update" for full release and version update info#_~\ ####_\ #####\\###1\#/Amazon Linux 2023 (ECS Optimized)/m/'For documentation, visit [URL_WITH_CREDENTIALS] ~]$ client_loop: send disconnect: Broken pipeukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-relay) $I...
|
85329
|
NULL
|
NULL
|
NULL
|
|
85331
|
2921
|
47
|
2026-05-28T12:18:42.044257+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970722044_m2.jpg...
|
Slack
|
Vasil Vasilev (DM) - Jiminny Inc - 2 new items - S Vasil Vasilev (DM) - Jiminny Inc - 2 new items - Slack...
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":6,"bounds":{"left":0.5056516,"top":0.058260176,"width":0.011968086,"height":0.028731046},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":6,"bounds":{"left":0.50299203,"top":0.10055866,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":8,"bounds":{"left":0.50664896,"top":0.13806863,"width":0.009973404,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":6,"bounds":{"left":0.50299203,"top":0.15482841,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":8,"bounds":{"left":0.50764626,"top":0.19233839,"width":0.007978723,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":6,"bounds":{"left":0.50299203,"top":0.20909816,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":8,"bounds":{"left":0.5053192,"top":0.24660814,"width":0.012965426,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5053192,"top":0.24660814,"width":0.0026595744,"height":0.011173184}},{"char_start":1,"char_count":7,"bounds":{"left":0.50764626,"top":0.24660814,"width":0.010638298,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":6,"bounds":{"left":0.50299203,"top":0.26336792,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":8,"bounds":{"left":0.50797874,"top":0.3008779,"width":0.0076462766,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50797874,"top":0.3008779,"width":0.0019946808,"height":0.011173184}},{"char_start":1,"char_count":4,"bounds":{"left":0.5099734,"top":0.3008779,"width":0.0056515955,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":6,"bounds":{"left":0.50299203,"top":0.31763768,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":8,"bounds":{"left":0.50731385,"top":0.35514766,"width":0.008643617,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50731385,"top":0.35514766,"width":0.0019946808,"height":0.011173184}},{"char_start":1,"char_count":4,"bounds":{"left":0.5093085,"top":0.35514766,"width":0.0066489363,"height":0.011173184}}],"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":6,"bounds":{"left":0.50299203,"top":0.3719074,"width":0.017287234,"height":0.054269753},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":8,"bounds":{"left":0.50731385,"top":0.4094174,"width":0.008976064,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50731385,"top":0.4094174,"width":0.0033244682,"height":0.011173184}},{"char_start":1,"char_count":3,"bounds":{"left":0.5106383,"top":0.4094174,"width":0.0056515955,"height":0.011173184}}],"role_description":"text"}]...
|
-8110235827886994960
|
-8111871699717769025
|
visual_change
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
SIUCKFV faVsco.is ~Projectv© VoiceConsentPrefix.phpe Not iicationsnObservers© AccountObserver.php© ActivityObserver.php© ContactObserver.php© [EMAIL]© ProfileObserver.phpSocialAccountObserver.phpUserObserver.php© UserRoleObserver.php> Co PoliciesC ProvidersQueueu ReoositonieCRutesvserviceActivityAiReodrEJ AvatarlealendarVIChRotonWindowHelp#12121 on JY-20963-fox-hService.phpphe helpers.phpDeleteObjectsTrait.phpOLeAkоkyscrco.on8 UserRoleObserverTest.phpC TextRelayServiceTest.phg(©) Hubspot.JournalPollinaService.ohp x$512Cass nuosoo vourhalroto hoseiv* Acquire exclusive polling lock to prevent aultiple instancesrivate function acquirePollingLock(): boolBullhorne copperimermobiacteDecorateActivityDummy› @ Helpersv im HibsodAccountSyncStrategyIhctiontContactSyncStrategy› DODTO331332333› E Fieldswhtaurnd© HubspotClientCredenti339@HubspotDealWebhook 340© HubspotJournalApiCie341© HubspotJournalPolling: 342© HubspotWebhookSubs© JournalApiResult.php© JournalßatchSizeLimitE© JournalEventTransforma MetadataOpportunitySyncStrategylmlpas nation* Rerease the potting lockrivate function releasePollingLock(): voidf...,* Force release the polling lock (for emergency situations)ublic function forceReleaseLock(): voidf...* Signat the polling service to stop gracefullyublic function requestStop(): void{...}©ActivityController.php© Kernel.phpE customlogA console (STAGING)# laravel.log700A12X9 A V-701-78278370420s786733712=213-74715- 716self:: LOCK,71772872272723ABREED732735734735736737TX: AUTONAND a.created_at > DATE_SGROUP BY u.id, v.email,ORDER RY sns count 0ESCselect * fron teans whereselect * fron roles:SELECTCONCAT(U.1d, CASE WHEsa.*,t.ouner_id FROM socieJOIN users u on u.id = stJOIN teans tWHERE v.tean_id = 1117 arSELECT * FROM activitiesSELECT * FROM activitiosSELECT * FROM crn_configuSELECT * FROM teans WHERHseleerron usens wherselect * fron playbooksselect * fron playbook_.ctselect * fron playbook_ctselect * fron crn_fieldsselect * fron crn_field.vSELECT * FROM crn_field_c* JOIN crn_fields f ON fo• JOIN activities a ON $6WHERE activity_id = 7993:AND f.crn_provider idSELECT * FROM activity_meselect * fron text_relaysselect * fron activitiesselect * fron users wherdselect x fron activitiesselect * fron accounts wiselect x fron teans wherdselect x fron activity_stHommeDMsActivityLaterJiminnY ….# backend# bugs# confusion-clinic# donut_time# engineering# general# happy_birthday8 infosec_internal_all# infra-changes# infrastructure_dev# jbu-team-info#jiminny-bg# platform-team# platform-ticketstoroduct launche# random# releases# support# thank-yous# the people of jimi..a? Direct messages€. Vasil Vasilev8. Stefka Stoyanova[. lliyana Netseva88. Stoyan Tomov® Petko KashinskiD, Galya Dimitrova E: Todor Stamatov. Steliyan Georgiev8 Ves(R. Mira. Nikolay Yankovdo James Graham2. Lukas Kovalik ....AppsInu cowoy 1o.lo.Q Describe what you are looking for€. Vasil Vasileve Messageru Add canvas&z Pins +или е друг driverVasil Vasilev 3:10PMне става, бях го направил така, и се счупиLukas Kovalik 3:10 PMами и при мен се чумешеDooywVasil Vasilev 3:11 PMoredis nonnsowa set/Skew, Svalue ['nx' = true. ex =6.0))Lukas Kovalik 3:11 PMVasil Vasilev 3:11 PMтака май не работеше Sresult - Redis:set(StestKey, StestValue, 'EX, Stti, "NX*); а така да Sresult = Redis::set(StestKey, StestValue, ['nx, 'ex' => Sttl])Lukas Kovalik 3-12.PMus soune soudVasil Vasilev 3:12 PMобратното може да еunkas Kovallk 2.12 pmможе и да брькамVasil Vasilev 3:12 PMRKOMMMASlockAcquired - Redis::set(self::POLLING_LOCK_KEY, Json_encode(SlockData), "EX", self::LOCK_TIL-SECONDS, "NX"))значи пьовото работиLukas Kovalik 313 PMда и в арр/Services/Crm/Hubspot/Client.php:103Vasil Vasilev 3:14 PMLukas Kovalik 3:16PMсега ги гледам, че и двете ми ги прави greyed out полседните два параметраVasil Vasilev 3-17 PMLukas Kovalik 3:17PMдано няма нещо промененоacllNachlov 247ovше си по сставя както e@# (A...
|
85330
|
NULL
|
NULL
|
NULL
|
|
85330
|
2921
|
46
|
2026-05-28T12:18:29.826935+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970709826_m2.jpg...
|
PhpStorm
|
faVsco.js – HubspotJournalPollingService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
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
45
1
41
66
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993
SELECT * FROM users WHERE id = 25061;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 994;
SELECT * FROM crm_profiles WHERE user_id = 25061;
select * from crm_configurations where id = 834;
SELECT * FROM teams WHERE id = 882;
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 = 882 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;
SELECT * FROM contacts where crm_configuration_id = 834;
SELECT * FROM opportunities WHERE team_id = 933
# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');
AND id IN (8482561,18352941,19042734,19232139,19445140,19472541);
SELECT * FROM opportunity_contacts
WHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 485; #
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
select crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id
where crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')
# and l.converted_at IS NOT NULL
;
# [PASSWORD_DOTS]
SELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')
and opportunity_id IS NULL
order by id desc;
SELECT * FROM teams WHERE id = 604; # 598
SELECT * FROM activities WHERE id = 74410828; # [EMAIL]
SELECT * FROM accounts WHERE id = 20068382;
SELECT * FROM accounts WHERE id = 35186038;
SELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;
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 = 559 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;
select * from sidekick_settings where team_id = 781;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100
SELECT * FROM crm_layouts WHERE crm_configuration_id = 711;
SELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL
and is_internal = 0 and status = 'completed'
order by id desc;
SELECT * FROM crm_layout_entities
WHERE crm_layout_id IN (2352, 2353);
;
SELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;
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 = 556 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;
SELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;
select * from contacts
where crm_configuration_id = 530
and crm_provider_id = 872252;
select * from activities where crm_configuration_id = 530
and user_id = 14343 and type like '%softphone%'
and created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);
SELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t
JOIN crm_configurations c ON t.id = c.team_id
WHERE t.status = 'active';
SELECT * FROM teams where id = 1091;
SELECT * FROM crm_configurations where team_id = 1091;
SELECT * FROM activity_providers where team_id = 1091;
SELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT * FROM teams WHERE name LIKE '%Leadventure%';
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 = 1091 and sa.provider = 'salesforce';
SELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812
SELECT * FROM teams where id = 862;
SELECT * FROM crm_configurations where team_id = 862;
SELECT * FROM activity_providers where team_id = 862;
SELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT t.id, crm.id, crm.provider, ap.* FROM teams t
join crm_configurations crm on t.id = crm.team_id
join activity_providers ap on t.id = ap.team_id
where t.status = 'active' and ap.is_enabled = 1
and crm.provider = 'hubspot'
and ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',
'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');
SELECT * FROM teams where id = 1068;
SELECT * FROM crm_configurations where team_id = 1068;
SELECT * FROM activity_providers where team_id = 1068;
SELECT * FROM activities a
where crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')
and a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'
)
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by a.id desc;
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 = 1068 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262
SELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;
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 = 882 and sa.provider = 'hubspot';
select * from crm_layouts where crm_configuration_id = 834;
select * from crm_layout_entities where crm_layout_id = 2780;
select * from crm_fields where id IN (321153,321192,321193,321194);
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
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 = 1057 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8
SELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20
SELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10
SELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #
SELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;
select * from users where team_id = 51; # 7783
SELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130
select * from activity_searches where user_id = 7783;
select * from activity_search_filters where activity_search_id IN (32291, 32292);
SELECT asf.activity_search_id, asf.id, asf.value
FROM activity_search_filters asf
WHERE asf.filter = 'group_id'
AND asf.value IN (
SELECT CONCAT(
HEX(SUBSTR(uuid, 5, 4)), '-',
HEX(SUBSTR(uuid, 3, 2)), '-',
HEX(SUBSTR(uuid, 1, 2)), '-',
HEX(SUBSTR(uuid, 9, 2)), '-',
HEX(SUBSTR(uuid, 11))
)
FROM groups
WHERE deleted_at IS NOT NULL
);
SELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where provider = 'hubspot';
SELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133
SELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null
# [PASSWORD_DOTS]
select * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';
select
cp.*
# DISTINCT t.id
# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields
FROM crm_profiles cp
JOIN crm_configurations crm on crm.id = cp.crm_configuration_id
JOIN users u on u.id = cp.user_id
JOIN teams t ON t.id = crm.team_id
WHERE crm.provider = 'salesforce' and t.status = 'active'
and cp.archived_at IS NULL and u.deleted_at IS NULL
and t.id NOT IN (1093)
and t.id = 2
and cp.contact_fields IS NULL;
# and c.crm_provider_id = '003Uu00000ojD4NIAU';
SELECT * FROM users WHERE id = 26484;
SELECT * FROM crm_profiles WHERE user_id = 26484;
SELECT * FROM social_accounts WHERE sociable_id = 26484;
SELECT * FROM crm_configurations where provider = 'salesforce';
select * from users where id IN (10022, 10403);
select * from users where team_id IN (526);
select * from teams where id IN (526, 532);
select * from crm_configurations where id IN (500, 516);
select * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);
select * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';
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 = 526 and sa.provider = 'salesforce';
select * from team_settings where team_id IN (526, 532);
select * from users where id IN (22824);
select * from crm_profiles where crm_configuration_id IN (1026);
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 = 1093 and sa.provider = 'salesforce';
select * from teams where id = 1099;
select * from users where id = 29643
select * from activity_processing_states;
SELECT * FROM teams where name LIKE '%Fare%'; # 233
SELECT * FROM opportunities where crm_configuration_id = 215
# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'
;
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 = 1088 and sa.provider = 'hubspot';
SELECT * FROM teams order by updated_at DESC
SELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account
select * from crm_configurations where provider = 'pipedrive';
select * from teams where id = 957;
select * from crm_configurations where id = 957;
SELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743
SELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;
select * from users where team_id = 1; # 26726 - Gabriela Dureva
SELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific
select * from activities where user_id = 26726 order by id desc;
select * from contacts where crm_configuration_id = 1
and email IN ('[EMAIL]', '[EMAIL]'); # 2094416, 2093620
SELECT * FROM contacts WHERE id = 6284931;
SELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id
WHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;
select * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);
select * from crm_configurations where id = 1;
43801692-1aeb-32ce-acba-5b80a479701a
44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b
405975c0-b3d0-7aaa-821f-09d59cae6dd1
4caf848d-4bed-2299-b248-7788d41f9fca
49bedc3f-f196-eef3-89c3-dea6a3b4aa63
43420989-a09d-b8f8-9806-c8bbf7a02aac
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 = 1 and sa.provider = 'salesforce';
SELECT * FROM activities WHERE id = 75461988;
SELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;
select * from contacts where id = 17900517;
select * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id
where crm.provider != 'salesforce';
select * from users where id = 21047;
SELECT * FROM crm_configurations WHERE id = 892;
SELECT * FROM teams WHERE id = 942;
select * from opportunities where team_id = 942 order by updated_at desc;
select * from contacts where team_id = 942 order by updated_at desc;
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 = 942 and sa.provider = 'hubspot';
SELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430
SELECT * FROM crm_configurations WHERE id = 1;
SELECT * FROM teams WHERE crm_id = 1;
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 = 1 and sa.provider = 'salesforce';
select id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1
SELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430
select * from teams where id = 852;
select * from groups where id = 2286;
select * from sidekick_settings where team_id = 852;
select * from default_activity_types where team_id = 852;
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1 AND u.deleted_at IS NULL
AND u.crm_required = 1
AND u.team_id = 1
ORDER BY u.team_id;
SELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (
18481
);
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1
AND u.deleted_at IS NULL
AND u.crm_required = 1
# AND u.team_id = 1
AND p.id IS NULL -- Move this condition to WHERE clause
ORDER BY u.team_id;
SELECT * FROM opportunities WHERE id = 20002609;
select * from teams where id = 1122; # Velatir, 29953 - [EMAIL]
select * from crm_configurations where id = 1060;
select * from crm_layouts where crm_configuration_id = 1060;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;
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 = 1122 and sa.provider = 'hubspot';
select * from opportunities where team_id = 1122 order by updated_at desc;
select * from crm_field_data where object_type = 'contact';
SELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262
SELECT
CONCAT(u.id, CASE WHEN u.id = t.own...
|
[{"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":"#12121 on JY-20963-fix-import-on-deleted-entity, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.11569149,"height":0.025538707},"on_screen":true,"help_text":"Pull request #12121 exists for current branch JY-20963-fix-import-on-deleted-entity","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.8374335,"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":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"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 'TextRelayServiceTest'","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 'TextRelayServiceTest'","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":"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":"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":"AXStaticText","text":"12","depth":4,"bounds":{"left":0.37865692,"top":0.15003991,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"9","depth":4,"bounds":{"left":0.39029256,"top":0.15003991,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39993352,"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.40724733,"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\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.41589096,"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.4245346,"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.43550533,"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.44414893,"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.45279256,"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.4637633,"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.47473404,"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.5013298,"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.51230055,"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.7237367,"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":"45","depth":4,"bounds":{"left":0.6938165,"top":0.123703115,"width":0.010305851,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.70611703,"top":0.123703115,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"41","depth":4,"bounds":{"left":0.71542555,"top":0.123703115,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"66","depth":4,"bounds":{"left":0.72706115,"top":0.123703115,"width":0.010305851,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.7390292,"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.7463431,"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":"SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993\nSELECT * FROM users WHERE id = 25061;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 994;\nSELECT * FROM crm_profiles WHERE user_id = 25061;\n\nselect * from crm_configurations where id = 834;\nSELECT * FROM teams WHERE id = 882;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;\n\nSELECT * FROM contacts where crm_configuration_id = 834;\nSELECT * FROM opportunities WHERE team_id = 933\n# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');\nAND id IN (8482561,18352941,19042734,19232139,19445140,19472541);\nSELECT * FROM opportunity_contacts\nWHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 485; #\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nselect crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id\nwhere crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')\n# and l.converted_at IS NOT NULL\n;\n\n# ********************************************************************\nSELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')\nand opportunity_id IS NULL\norder by id desc;\n\nSELECT * FROM teams WHERE id = 604; # 598\nSELECT * FROM activities WHERE id = 74410828; # chelseaw@allvoices.co\nSELECT * FROM accounts WHERE id = 20068382;\nSELECT * FROM accounts WHERE id = 35186038;\n\nSELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 559 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;\nselect * from sidekick_settings where team_id = 781;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100\n\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 711;\nSELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL\nand is_internal = 0 and status = 'completed'\norder by id desc;\n\nSELECT * FROM crm_layout_entities\nWHERE crm_layout_id IN (2352, 2353);\n;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 556 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;\nSELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;\nselect * from contacts\nwhere crm_configuration_id = 530\nand crm_provider_id = 872252;\n\nselect * from activities where crm_configuration_id = 530\nand user_id = 14343 and type like '%softphone%'\nand created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);\n\n\nSELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t\nJOIN crm_configurations c ON t.id = c.team_id\nWHERE t.status = 'active';\n\nSELECT * FROM teams where id = 1091;\nSELECT * FROM crm_configurations where team_id = 1091;\nSELECT * FROM activity_providers where team_id = 1091;\nSELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT * FROM teams WHERE name LIKE '%Leadventure%';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1091 and sa.provider = 'salesforce';\n\nSELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812\nSELECT * FROM teams where id = 862;\nSELECT * FROM crm_configurations where team_id = 862;\nSELECT * FROM activity_providers where team_id = 862;\nSELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT t.id, crm.id, crm.provider, ap.* FROM teams t\njoin crm_configurations crm on t.id = crm.team_id\njoin activity_providers ap on t.id = ap.team_id\nwhere t.status = 'active' and ap.is_enabled = 1\nand crm.provider = 'hubspot'\nand ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',\n 'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');\n\nSELECT * FROM teams where id = 1068;\nSELECT * FROM crm_configurations where team_id = 1068;\nSELECT * FROM activity_providers where team_id = 1068;\n\nSELECT * FROM activities a\nwhere crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')\nand a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'\n )\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by a.id desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1068 and sa.provider = 'hubspot';\n\n# ********************************************************************\n# ********************************************************************\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262\nSELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\nselect * from crm_layouts where crm_configuration_id = 834;\nselect * from crm_layout_entities where crm_layout_id = 2780;\nselect * from crm_fields where id IN (321153,321192,321193,321194);\n\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1057 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8\n\nSELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20\n\nSELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10\n\nSELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #\n\nSELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;\nselect * from users where team_id = 51; # 7783\nSELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130\nselect * from activity_searches where user_id = 7783;\nselect * from activity_search_filters where activity_search_id IN (32291, 32292);\n\nSELECT asf.activity_search_id, asf.id, asf.value\nFROM activity_search_filters asf\nWHERE asf.filter = 'group_id'\nAND asf.value IN (\n SELECT CONCAT(\n HEX(SUBSTR(uuid, 5, 4)), '-',\n HEX(SUBSTR(uuid, 3, 2)), '-',\n HEX(SUBSTR(uuid, 1, 2)), '-',\n HEX(SUBSTR(uuid, 9, 2)), '-',\n HEX(SUBSTR(uuid, 11))\n )\n FROM groups\n WHERE deleted_at IS NOT NULL\n);\n\nSELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th\n# ********************************************************************\nSELECT * FROM crm_configurations where provider = 'hubspot';\nSELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133\nSELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null\n# ********************************************************************\n\nselect * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';\nselect\n cp.*\n# DISTINCT t.id\n# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields\nFROM crm_profiles cp\nJOIN crm_configurations crm on crm.id = cp.crm_configuration_id\nJOIN users u on u.id = cp.user_id\nJOIN teams t ON t.id = crm.team_id\nWHERE crm.provider = 'salesforce' and t.status = 'active'\n and cp.archived_at IS NULL and u.deleted_at IS NULL\n and t.id NOT IN (1093)\n and t.id = 2\n and cp.contact_fields IS NULL;\n# and c.crm_provider_id = '003Uu00000ojD4NIAU';\n\nSELECT * FROM users WHERE id = 26484;\nSELECT * FROM crm_profiles WHERE user_id = 26484;\nSELECT * FROM social_accounts WHERE sociable_id = 26484;\nSELECT * FROM crm_configurations where provider = 'salesforce';\nselect * from users where id IN (10022, 10403);\nselect * from users where team_id IN (526);\nselect * from teams where id IN (526, 532);\nselect * from crm_configurations where id IN (500, 516);\nselect * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);\nselect * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 526 and sa.provider = 'salesforce';\nselect * from team_settings where team_id IN (526, 532);\n\nselect * from users where id IN (22824);\nselect * from crm_profiles where crm_configuration_id IN (1026);\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1093 and sa.provider = 'salesforce';\n\nselect * from teams where id = 1099;\nselect * from users where id = 29643\n\nselect * from activity_processing_states;\n\nSELECT * FROM teams where name LIKE '%Fare%'; # 233\nSELECT * FROM opportunities where crm_configuration_id = 215\n# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1088 and sa.provider = 'hubspot';\n\nSELECT * FROM teams order by updated_at DESC\nSELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account\n\nselect * from crm_configurations where provider = 'pipedrive';\n\nselect * from teams where id = 957;\nselect * from crm_configurations where id = 957;\n\nSELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743\nSELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;\n\nselect * from users where team_id = 1; # 26726 - Gabriela Dureva\nSELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific\nselect * from activities where user_id = 26726 order by id desc;\nselect * from contacts where crm_configuration_id = 1\nand email IN ('charlotte.ward@prolific.com', 'frankie.bryant@prolific.com'); # 2094416, 2093620\nSELECT * FROM contacts WHERE id = 6284931;\n\nSELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id\nWHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;\n\nselect * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);\nselect * from crm_configurations where id = 1;\n\n43801692-1aeb-32ce-acba-5b80a479701a\n44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b\n405975c0-b3d0-7aaa-821f-09d59cae6dd1\n4caf848d-4bed-2299-b248-7788d41f9fca\n49bedc3f-f196-eef3-89c3-dea6a3b4aa63\n43420989-a09d-b8f8-9806-c8bbf7a02aac\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nSELECT * FROM activities WHERE id = 75461988;\n\nSELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;\n\nselect * from contacts where id = 17900517;\n\nselect * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id\nwhere crm.provider != 'salesforce';\n\nselect * from users where id = 21047;\nSELECT * FROM crm_configurations WHERE id = 892;\nSELECT * FROM teams WHERE id = 942;\nselect * from opportunities where team_id = 942 order by updated_at desc;\nselect * from contacts where team_id = 942 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 942 and sa.provider = 'hubspot';\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430\nSELECT * FROM crm_configurations WHERE id = 1;\nSELECT * FROM teams WHERE crm_id = 1;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nselect id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1\nSELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430\n\nselect * from teams where id = 852;\nselect * from groups where id = 2286;\nselect * from sidekick_settings where team_id = 852;\nselect * from default_activity_types where team_id = 852;\n\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1 AND u.deleted_at IS NULL\nAND u.crm_required = 1\nAND u.team_id = 1\nORDER BY u.team_id;\n\nSELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (\n18481\n );\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1\n AND u.deleted_at IS NULL\n AND u.crm_required = 1\n# AND u.team_id = 1\n AND p.id IS NULL -- Move this condition to WHERE clause\nORDER BY u.team_id;\n\nSELECT * FROM opportunities WHERE id = 20002609;\nselect * from teams where id = 1122; # Velatir, 29953 - christian@velatir.com\nselect * from crm_configurations where id = 1060;\nselect * from crm_layouts where crm_configuration_id = 1060;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1122 and sa.provider = 'hubspot';\nselect * from opportunities where team_id = 1122 order by updated_at desc;\n\nselect * from crm_field_data where object_type = 'contact';\n\nSELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 248 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS\nSELECT * FROM users where id = 24115;\nSELECT * FROM accounts where id = 4002896;\nSELECT * FROM teams WHERE name LIKE '%adswerve%';\nSELECT * FROM opportunities where crm_configuration_id = 230 AND crm_provider_id IN (\"0069N000003GIQ9QAO\",\"0061r000019yGP9AAM\",\"0066900001S2KWlAAN\",\"0066900001TDpj2AAD\",\"0066900001b8uEwAAI\",\"0069N000001rQi0QAE\",\"006QF00000KD40mYAD\",\"006QF00000LzpRJYAZ\",\"0069N000002uomtQAA\",\"0069N000002xlMLQAY\",\"0066900001NV6ubAAD\",\"0061r00001HJp45AAD\",\"006QF00000uTlUoYAK\",\"006QF00000v0bZqYAI\");\nSELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203\n\nSELECT u.id, u.email, ac.name, a.* FROM activities a\nJOIN users u ON a.user_id = u.id\nJOIN accounts ac ON a.account_id = ac.id\nWHERE\nuuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or\nuuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or\nuuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;\n\nselect * from users where id = 5825;\nSELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;\n\nselect * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;\n19594, 862\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 862 and sa.provider = 'salesforce';\n\nselect * from automated_reports where id = 36;\nselect ar.frequency, r.*, ar.* from automated_report_results r\njoin automated_reports ar on r.report_id = ar.id\nwhere ar.frequency != 'one_off';\n\nselect s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;\nselect * from nudges n where n.activity_search_id\n\nselect * from teams where created_at > '2026-03-09';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;\n\nselect * from users where team_id = 1 and name like '%Lukas%'; # 7160\n\nSELECT * FROM teams WHERE id = 575;\nselect * from opportunities where team_id = 575;\nSELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,\nselect * from opportunities where team_id = 1126;\nSELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,\nselect * from opportunities where team_id = 1125;\nselect * from contacts c\nwhere c.team_id = 882;\n\nSELECT * FROM activities WHERE id = 76822967;\nSELECT * FROM crm_profiles WHERE user_id = 15440;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 555;\nSELECT * FROM crm_configurations WHERE id = 555;\nSELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 581 and sa.provider = 'salesforce';\n\nSELECT * FROM automated_report_results order by id desc;\n\nselect * from features;\nselect * from team_features where feature_id = 40;\n\nselect * from teams where id = 556;\n\nselect * from automated_reports;\nwhere id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , [\"pdf\",\"podcast\"]\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\nselect * from automated_report_results order by id desc;\nSELECT * FROM automated_report_results WHERE id = 1919;\n\nselect * from automated_report_results WHERE report_id = 54;\n\nselect * from opportunities where id = 7594349;\n\nSELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067 - jiminnyintegration@lesmills.com\nselect * from playbooks where team_id = 711; # event 226147\nSELECT * FROM playbook_categories WHERE playbook_id = 5515;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event';\nSELECT * FROM crm_fields WHERE id = 226147;\nSELECT * FROM crm_field_values WHERE crm_field_id = 226147;\n\nSELECT * FROM crm_configurations WHERE id = 692;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 711 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles cp JOIN users u on u.id = cp.user_id WHERE u.team_id = 711;\n\nselect * from leads;\n\nselect * from calendars;\n\nSELECT\n t.id AS team_id,\n t.name,\n LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1)) AS calendar_domain\nFROM teams t\nJOIN users u ON u.team_id = t.id\nJOIN calendars c ON c.user_id = u.id AND c.status = 'active' AND c.calendar_provider_id LIKE '%@%'\nLEFT JOIN team_domains td\n ON td.team_id = t.id\n AND td.deleted_at IS NULL\n AND td.domain = LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1))\nGROUP BY t.id, t.name, calendar_domain\nORDER BY t.name, calendar_domain;\n\nselect * from users u join calendars c on c.user_id = u.id\nwhere u.team_id = 882;\n\n\nselect * from activities where id = 74049485; # team 563 crm 537\nselect * from activities where id = 73272382; # team 563 crm 537\nselect * from activities where id = 64400389; # team 563 crm 537\nselect * from activities where id = 58081273; # team 563 crm 537\nselect * from activities where id = 54520297; # team 563 crm 537\nselect * from participants where activity_id = 58081273;\n\nselect * from activities where crm_configuration_id = 537 and provider = 'aircall'\nand account_id = 19003658 order by updated_at desc;\n\nselect * from contacts where crm_configuration_id = 537 and id = 35957759;\nselect * from accounts where crm_configuration_id = 537 and id = 19003658;\n\nselect * from automated_report_results where id = 1976;\nselect * from automated_reports where id = 583;\nselect * from activity_searches where id = 87714;\nselect * from activity_search_filters where activity_search_id = 87714;\n\nSELECT * FROM activities WHERE uuid_to_bin('8827f672-202d-4162-9d04-73ff5f0566a9') = uuid\nor uuid_to_bin('47842446-af51-4bcb-854f-cc6560290101') = uuid;\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot';\nselect * from rate_limits;\n\nselect * from automated_report_results where media_type = 'pdf' and status = 2\nand id IN (18, 1872);\nselect * from automated_reports where id = 54;\nSELECT * FROM users WHERE id IN (24623,29443,29613);\n\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\n\nselect * from text_relays where created_at > '2026-05-01';\nand id IN (32415, 32416);\n# and id = 32412;\n\nselect * from users where team_id = 2 and email like '%scott%' and id = 29510;\n\nSELECT * FROM activities WHERE uuid_to_bin('67cebfc2-ed56-44a2-8c68-7a0286ed8618') = uuid; # 79763436\n\nSELECT email_provider_id, COUNT(*) as count, GROUP_CONCAT(id) as ids, GROUP_CONCAT(status) as statuses\nFROM text_relays\nWHERE email_provider_id IN ('19e2027868a64b42', '19e2033ed8ea6b10')\nGROUP BY email_provider_id;\nSELECT id, status, telephony_provider_id, created_at\nFROM activities\nWHERE id IN (80028719, 80028846);\nSELECT id, status, code, email_sent_at, created_at, updated_at\nFROM text_relays\nWHERE id IN (32415, 32416);\nSELECT id, status, code, sender, recipient, created_at\nFROM text_relays\nWHERE sender LIKE '%mario.georgiev%' OR sender LIKE '%stoyan.tomov%'\nORDER BY created_at DESC\nLIMIT 10;\n\nSELECT id, uuid, status, code, sender, recipient, created_at, updated_at\nFROM text_relays\nWHERE uuid = uuid_to_bin('0626141c-27a6-4d8c-aff8-7c8020a2c656');\n\n# ***************\nSELECT DISTINCT u.id, u.email, u.name, u.softphone_number, COUNT(a.id) as sms_count\nFROM users u\nINNER JOIN activities a ON u.id = .user_id\nWHERE a.type LIKE 'sms%'\nAND a.created_at > DATE_SUB(NOW(), INTERVAL 30 DAY)\nGROUP BY u.id, u.email, u.name, u.softphone_number\nORDER BY sms_count DESC;\n\nselect * from teams where id = 1;\n\nselect * from roles;\n\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1117 and sa.provider = 'hubspot';\nSELECT * FROM activities WHERE uuid_to_bin('8024fffb-2df7-4017-91f4-d9f896050248') = uuid; # 79933459 YES\nSELECT * FROM activities WHERE uuid_to_bin('25529043-8094-4781-927f-4f4da2a8185c') = uuid; # 80186192 NO\nSELECT * FROM crm_configurations WHERE id = 1053;\nSELECT * FROM teams WHERE id = 1117;\nselect * from users where id = 30249;\nselect * from playbooks where id = 5473;\nselect * from playbook_categories where id = 43783;\nselect * from playbook_categories where playbook_id = 5473;\nselect * from crm_fields where id = 659242;\nselect * from crm_field_values where crm_field_id = 659242;\n\nSELECT * FROM crm_field_data fd\n# JOIN crm_fields f ON fd.crm_field_id = f.id\n# JOIN activities a ON fd.activity_id = a.id\nWHERE activity_id = 79933459\n# AND f.crm_provider_id = 'hs_activity_type';\n\n\nSELECT * FROM activity_messages;\nselect * from text_relays where created_at > '2026-05-01';\nselect * from activities where user_id IN (7160, 18608) and created_at > '2026-05-22' order by id desc;\nselect * from users where team_id = 1 and id IN (18608, 13934, 7160);\nselect * from activities where user_id = 7160 order by id desc limit 10;\n\nselect * from accounts where team_id = 1 and name = 'Column5';\n\nselect * from users where name like '%Subra%'; # 31054, 1117\nselect * from teams where id = 1117;\nselect * from activity_searches where user_id = 31054;\nselect * from activity_search_filters where activity_search_id IN (88882, 88902);","depth":4,"on_screen":true,"value":"SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993\nSELECT * FROM users WHERE id = 25061;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 994;\nSELECT * FROM crm_profiles WHERE user_id = 25061;\n\nselect * from crm_configurations where id = 834;\nSELECT * FROM teams WHERE id = 882;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;\n\nSELECT * FROM contacts where crm_configuration_id = 834;\nSELECT * FROM opportunities WHERE team_id = 933\n# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');\nAND id IN (8482561,18352941,19042734,19232139,19445140,19472541);\nSELECT * FROM opportunity_contacts\nWHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 485; #\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nselect crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id\nwhere crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')\n# and l.converted_at IS NOT NULL\n;\n\n# ********************************************************************\nSELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')\nand opportunity_id IS NULL\norder by id desc;\n\nSELECT * FROM teams WHERE id = 604; # 598\nSELECT * FROM activities WHERE id = 74410828; # chelseaw@allvoices.co\nSELECT * FROM accounts WHERE id = 20068382;\nSELECT * FROM accounts WHERE id = 35186038;\n\nSELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 559 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;\nselect * from sidekick_settings where team_id = 781;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100\n\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 711;\nSELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL\nand is_internal = 0 and status = 'completed'\norder by id desc;\n\nSELECT * FROM crm_layout_entities\nWHERE crm_layout_id IN (2352, 2353);\n;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 556 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;\nSELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;\nselect * from contacts\nwhere crm_configuration_id = 530\nand crm_provider_id = 872252;\n\nselect * from activities where crm_configuration_id = 530\nand user_id = 14343 and type like '%softphone%'\nand created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);\n\n\nSELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t\nJOIN crm_configurations c ON t.id = c.team_id\nWHERE t.status = 'active';\n\nSELECT * FROM teams where id = 1091;\nSELECT * FROM crm_configurations where team_id = 1091;\nSELECT * FROM activity_providers where team_id = 1091;\nSELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT * FROM teams WHERE name LIKE '%Leadventure%';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1091 and sa.provider = 'salesforce';\n\nSELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812\nSELECT * FROM teams where id = 862;\nSELECT * FROM crm_configurations where team_id = 862;\nSELECT * FROM activity_providers where team_id = 862;\nSELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT t.id, crm.id, crm.provider, ap.* FROM teams t\njoin crm_configurations crm on t.id = crm.team_id\njoin activity_providers ap on t.id = ap.team_id\nwhere t.status = 'active' and ap.is_enabled = 1\nand crm.provider = 'hubspot'\nand ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',\n 'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');\n\nSELECT * FROM teams where id = 1068;\nSELECT * FROM crm_configurations where team_id = 1068;\nSELECT * FROM activity_providers where team_id = 1068;\n\nSELECT * FROM activities a\nwhere crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')\nand a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'\n )\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by a.id desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1068 and sa.provider = 'hubspot';\n\n# ********************************************************************\n# ********************************************************************\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262\nSELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\nselect * from crm_layouts where crm_configuration_id = 834;\nselect * from crm_layout_entities where crm_layout_id = 2780;\nselect * from crm_fields where id IN (321153,321192,321193,321194);\n\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1057 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8\n\nSELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20\n\nSELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10\n\nSELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #\n\nSELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;\nselect * from users where team_id = 51; # 7783\nSELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130\nselect * from activity_searches where user_id = 7783;\nselect * from activity_search_filters where activity_search_id IN (32291, 32292);\n\nSELECT asf.activity_search_id, asf.id, asf.value\nFROM activity_search_filters asf\nWHERE asf.filter = 'group_id'\nAND asf.value IN (\n SELECT CONCAT(\n HEX(SUBSTR(uuid, 5, 4)), '-',\n HEX(SUBSTR(uuid, 3, 2)), '-',\n HEX(SUBSTR(uuid, 1, 2)), '-',\n HEX(SUBSTR(uuid, 9, 2)), '-',\n HEX(SUBSTR(uuid, 11))\n )\n FROM groups\n WHERE deleted_at IS NOT NULL\n);\n\nSELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th\n# ********************************************************************\nSELECT * FROM crm_configurations where provider = 'hubspot';\nSELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133\nSELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null\n# ********************************************************************\n\nselect * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';\nselect\n cp.*\n# DISTINCT t.id\n# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields\nFROM crm_profiles cp\nJOIN crm_configurations crm on crm.id = cp.crm_configuration_id\nJOIN users u on u.id = cp.user_id\nJOIN teams t ON t.id = crm.team_id\nWHERE crm.provider = 'salesforce' and t.status = 'active'\n and cp.archived_at IS NULL and u.deleted_at IS NULL\n and t.id NOT IN (1093)\n and t.id = 2\n and cp.contact_fields IS NULL;\n# and c.crm_provider_id = '003Uu00000ojD4NIAU';\n\nSELECT * FROM users WHERE id = 26484;\nSELECT * FROM crm_profiles WHERE user_id = 26484;\nSELECT * FROM social_accounts WHERE sociable_id = 26484;\nSELECT * FROM crm_configurations where provider = 'salesforce';\nselect * from users where id IN (10022, 10403);\nselect * from users where team_id IN (526);\nselect * from teams where id IN (526, 532);\nselect * from crm_configurations where id IN (500, 516);\nselect * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);\nselect * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 526 and sa.provider = 'salesforce';\nselect * from team_settings where team_id IN (526, 532);\n\nselect * from users where id IN (22824);\nselect * from crm_profiles where crm_configuration_id IN (1026);\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1093 and sa.provider = 'salesforce';\n\nselect * from teams where id = 1099;\nselect * from users where id = 29643\n\nselect * from activity_processing_states;\n\nSELECT * FROM teams where name LIKE '%Fare%'; # 233\nSELECT * FROM opportunities where crm_configuration_id = 215\n# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1088 and sa.provider = 'hubspot';\n\nSELECT * FROM teams order by updated_at DESC\nSELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account\n\nselect * from crm_configurations where provider = 'pipedrive';\n\nselect * from teams where id = 957;\nselect * from crm_configurations where id = 957;\n\nSELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743\nSELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;\n\nselect * from users where team_id = 1; # 26726 - Gabriela Dureva\nSELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific\nselect * from activities where user_id = 26726 order by id desc;\nselect * from contacts where crm_configuration_id = 1\nand email IN ('charlotte.ward@prolific.com', 'frankie.bryant@prolific.com'); # 2094416, 2093620\nSELECT * FROM contacts WHERE id = 6284931;\n\nSELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id\nWHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;\n\nselect * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);\nselect * from crm_configurations where id = 1;\n\n43801692-1aeb-32ce-acba-5b80a479701a\n44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b\n405975c0-b3d0-7aaa-821f-09d59cae6dd1\n4caf848d-4bed-2299-b248-7788d41f9fca\n49bedc3f-f196-eef3-89c3-dea6a3b4aa63\n43420989-a09d-b8f8-9806-c8bbf7a02aac\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nSELECT * FROM activities WHERE id = 75461988;\n\nSELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;\n\nselect * from contacts where id = 17900517;\n\nselect * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id\nwhere crm.provider != 'salesforce';\n\nselect * from users where id = 21047;\nSELECT * FROM crm_configurations WHERE id = 892;\nSELECT * FROM teams WHERE id = 942;\nselect * from opportunities where team_id = 942 order by updated_at desc;\nselect * from contacts where team_id = 942 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 942 and sa.provider = 'hubspot';\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430\nSELECT * FROM crm_configurations WHERE id = 1;\nSELECT * FROM teams WHERE crm_id = 1;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nselect id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1\nSELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430\n\nselect * from teams where id = 852;\nselect * from groups where id = 2286;\nselect * from sidekick_settings where team_id = 852;\nselect * from default_activity_types where team_id = 852;\n\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1 AND u.deleted_at IS NULL\nAND u.crm_required = 1\nAND u.team_id = 1\nORDER BY u.team_id;\n\nSELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (\n18481\n );\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1\n AND u.deleted_at IS NULL\n AND u.crm_required = 1\n# AND u.team_id = 1\n AND p.id IS NULL -- Move this condition to WHERE clause\nORDER BY u.team_id;\n\nSELECT * FROM opportunities WHERE id = 20002609;\nselect * from teams where id = 1122; # Velatir, 29953 - christian@velatir.com\nselect * from crm_configurations where id = 1060;\nselect * from crm_layouts where crm_configuration_id = 1060;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1122 and sa.provider = 'hubspot';\nselect * from opportunities where team_id = 1122 order by updated_at desc;\n\nselect * from crm_field_data where object_type = 'contact';\n\nSELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 248 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS\nSELECT * FROM users where id = 24115;\nSELECT * FROM accounts where id = 4002896;\nSELECT * FROM teams WHERE name LIKE '%adswerve%';\nSELECT * FROM opportunities where crm_configuration_id = 230 AND crm_provider_id IN (\"0069N000003GIQ9QAO\",\"0061r000019yGP9AAM\",\"0066900001S2KWlAAN\",\"0066900001TDpj2AAD\",\"0066900001b8uEwAAI\",\"0069N000001rQi0QAE\",\"006QF00000KD40mYAD\",\"006QF00000LzpRJYAZ\",\"0069N000002uomtQAA\",\"0069N000002xlMLQAY\",\"0066900001NV6ubAAD\",\"0061r00001HJp45AAD\",\"006QF00000uTlUoYAK\",\"006QF00000v0bZqYAI\");\nSELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203\n\nSELECT u.id, u.email, ac.name, a.* FROM activities a\nJOIN users u ON a.user_id = u.id\nJOIN accounts ac ON a.account_id = ac.id\nWHERE\nuuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or\nuuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or\nuuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;\n\nselect * from users where id = 5825;\nSELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;\n\nselect * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;\n19594, 862\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 862 and sa.provider = 'salesforce';\n\nselect * from automated_reports where id = 36;\nselect ar.frequency, r.*, ar.* from automated_report_results r\njoin automated_reports ar on r.report_id = ar.id\nwhere ar.frequency != 'one_off';\n\nselect s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;\nselect * from nudges n where n.activity_search_id\n\nselect * from teams where created_at > '2026-03-09';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;\n\nselect * from users where team_id = 1 and name like '%Lukas%'; # 7160\n\nSELECT * FROM teams WHERE id = 575;\nselect * from opportunities where team_id = 575;\nSELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,\nselect * from opportunities where team_id = 1126;\nSELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,\nselect * from opportunities where team_id = 1125;\nselect * from contacts c\nwhere c.team_id = 882;\n\nSELECT * FROM activities WHERE id = 76822967;\nSELECT * FROM crm_profiles WHERE user_id = 15440;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 555;\nSELECT * FROM crm_configurations WHERE id = 555;\nSELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 581 and sa.provider = 'salesforce';\n\nSELECT * FROM automated_report_results order by id desc;\n\nselect * from features;\nselect * from team_features where feature_id = 40;\n\nselect * from teams where id = 556;\n\nselect * from automated_reports;\nwhere id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , [\"pdf\",\"podcast\"]\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\nselect * from automated_report_results order by id desc;\nSELECT * FROM automated_report_results WHERE id = 1919;\n\nselect * from automated_report_results WHERE report_id = 54;\n\nselect * from opportunities where id = 7594349;\n\nSELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067 - jiminnyintegration@lesmills.com\nselect * from playbooks where team_id = 711; # event 226147\nSELECT * FROM playbook_categories WHERE playbook_id = 5515;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event';\nSELECT * FROM crm_fields WHERE id = 226147;\nSELECT * FROM crm_field_values WHERE crm_field_id = 226147;\n\nSELECT * FROM crm_configurations WHERE id = 692;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 711 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles cp JOIN users u on u.id = cp.user_id WHERE u.team_id = 711;\n\nselect * from leads;\n\nselect * from calendars;\n\nSELECT\n t.id AS team_id,\n t.name,\n LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1)) AS calendar_domain\nFROM teams t\nJOIN users u ON u.team_id = t.id\nJOIN calendars c ON c.user_id = u.id AND c.status = 'active' AND c.calendar_provider_id LIKE '%@%'\nLEFT JOIN team_domains td\n ON td.team_id = t.id\n AND td.deleted_at IS NULL\n AND td.domain = LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1))\nGROUP BY t.id, t.name, calendar_domain\nORDER BY t.name, calendar_domain;\n\nselect * from users u join calendars c on c.user_id = u.id\nwhere u.team_id = 882;\n\n\nselect * from activities where id = 74049485; # team 563 crm 537\nselect * from activities where id = 73272382; # team 563 crm 537\nselect * from activities where id = 64400389; # team 563 crm 537\nselect * from activities where id = 58081273; # team 563 crm 537\nselect * from activities where id = 54520297; # team 563 crm 537\nselect * from participants where activity_id = 58081273;\n\nselect * from activities where crm_configuration_id = 537 and provider = 'aircall'\nand account_id = 19003658 order by updated_at desc;\n\nselect * from contacts where crm_configuration_id = 537 and id = 35957759;\nselect * from accounts where crm_configuration_id = 537 and id = 19003658;\n\nselect * from automated_report_results where id = 1976;\nselect * from automated_reports where id = 583;\nselect * from activity_searches where id = 87714;\nselect * from activity_search_filters where activity_search_id = 87714;\n\nSELECT * FROM activities WHERE uuid_to_bin('8827f672-202d-4162-9d04-73ff5f0566a9') = uuid\nor uuid_to_bin('47842446-af51-4bcb-854f-cc6560290101') = uuid;\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot';\nselect * from rate_limits;\n\nselect * from automated_report_results where media_type = 'pdf' and status = 2\nand id IN (18, 1872);\nselect * from automated_reports where id = 54;\nSELECT * FROM users WHERE id IN (24623,29443,29613);\n\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\n\nselect * from text_relays where created_at > '2026-05-01';\nand id IN (32415, 32416);\n# and id = 32412;\n\nselect * from users where team_id = 2 and email like '%scott%' and id = 29510;\n\nSELECT * FROM activities WHERE uuid_to_bin('67cebfc2-ed56-44a2-8c68-7a0286ed8618') = uuid; # 79763436\n\nSELECT email_provider_id, COUNT(*) as count, GROUP_CONCAT(id) as ids, GROUP_CONCAT(status) as statuses\nFROM text_relays\nWHERE email_provider_id IN ('19e2027868a64b42', '19e2033ed8ea6b10')\nGROUP BY email_provider_id;\nSELECT id, status, telephony_provider_id, created_at\nFROM activities\nWHERE id IN (80028719, 80028846);\nSELECT id, status, code, email_sent_at, created_at, updated_at\nFROM text_relays\nWHERE id IN (32415, 32416);\nSELECT id, status, code, sender, recipient, created_at\nFROM text_relays\nWHERE sender LIKE '%mario.georgiev%' OR sender LIKE '%stoyan.tomov%'\nORDER BY created_at DESC\nLIMIT 10;\n\nSELECT id, uuid, status, code, sender, recipient, created_at, updated_at\nFROM text_relays\nWHERE uuid = uuid_to_bin('0626141c-27a6-4d8c-aff8-7c8020a2c656');\n\n# ***************\nSELECT DISTINCT u.id, u.email, u.name, u.softphone_number, COUNT(a.id) as sms_count\nFROM users u\nINNER JOIN activities a ON u.id = .user_id\nWHERE a.type LIKE 'sms%'\nAND a.created_at > DATE_SUB(NOW(), INTERVAL 30 DAY)\nGROUP BY u.id, u.email, u.name, u.softphone_number\nORDER BY sms_count DESC;\n\nselect * from teams where id = 1;\n\nselect * from roles;\n\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1117 and sa.provider = 'hubspot';\nSELECT * FROM activities WHERE uuid_to_bin('8024fffb-2df7-4017-91f4-d9f896050248') = uuid; # 79933459 YES\nSELECT * FROM activities WHERE uuid_to_bin('25529043-8094-4781-927f-4f4da2a8185c') = uuid; # 80186192 NO\nSELECT * FROM crm_configurations WHERE id = 1053;\nSELECT * FROM teams WHERE id = 1117;\nselect * from users where id = 30249;\nselect * from playbooks where id = 5473;\nselect * from playbook_categories where id = 43783;\nselect * from playbook_categories where playbook_id = 5473;\nselect * from crm_fields where id = 659242;\nselect * from crm_field_values where crm_field_id = 659242;\n\nSELECT * FROM crm_field_data fd\n# JOIN crm_fields f ON fd.crm_field_id = f.id\n# JOIN activities a ON fd.activity_id = a.id\nWHERE activity_id = 79933459\n# AND f.crm_provider_id = 'hs_activity_type';\n\n\nSELECT * FROM activity_messages;\nselect * from text_relays where created_at > '2026-05-01';\nselect * from activities where user_id IN (7160, 18608) and created_at > '2026-05-22' order by id desc;\nselect * from users where team_id = 1 and id IN (18608, 13934, 7160);\nselect * from activities where user_id = 7160 order by id desc limit 10;\n\nselect * from accounts where team_id = 1 and name = 'Column5';\n\nselect * from users where name like '%Subra%'; # 31054, 1117\nselect * from teams where id = 1117;\nselect * from activity_searches where user_id = 31054;\nselect * from activity_search_filters where activity_search_id IN (88882, 88902);","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}]...
|
2526630547225287665
|
1137771414947247693
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
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
45
1
41
66
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993
SELECT * FROM users WHERE id = 25061;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 994;
SELECT * FROM crm_profiles WHERE user_id = 25061;
select * from crm_configurations where id = 834;
SELECT * FROM teams WHERE id = 882;
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 = 882 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;
SELECT * FROM contacts where crm_configuration_id = 834;
SELECT * FROM opportunities WHERE team_id = 933
# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');
AND id IN (8482561,18352941,19042734,19232139,19445140,19472541);
SELECT * FROM opportunity_contacts
WHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 485; #
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
select crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id
where crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')
# and l.converted_at IS NOT NULL
;
# [PASSWORD_DOTS]
SELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')
and opportunity_id IS NULL
order by id desc;
SELECT * FROM teams WHERE id = 604; # 598
SELECT * FROM activities WHERE id = 74410828; # [EMAIL]
SELECT * FROM accounts WHERE id = 20068382;
SELECT * FROM accounts WHERE id = 35186038;
SELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;
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 = 559 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;
select * from sidekick_settings where team_id = 781;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100
SELECT * FROM crm_layouts WHERE crm_configuration_id = 711;
SELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL
and is_internal = 0 and status = 'completed'
order by id desc;
SELECT * FROM crm_layout_entities
WHERE crm_layout_id IN (2352, 2353);
;
SELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;
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 = 556 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;
SELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;
select * from contacts
where crm_configuration_id = 530
and crm_provider_id = 872252;
select * from activities where crm_configuration_id = 530
and user_id = 14343 and type like '%softphone%'
and created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);
SELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t
JOIN crm_configurations c ON t.id = c.team_id
WHERE t.status = 'active';
SELECT * FROM teams where id = 1091;
SELECT * FROM crm_configurations where team_id = 1091;
SELECT * FROM activity_providers where team_id = 1091;
SELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT * FROM teams WHERE name LIKE '%Leadventure%';
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 = 1091 and sa.provider = 'salesforce';
SELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812
SELECT * FROM teams where id = 862;
SELECT * FROM crm_configurations where team_id = 862;
SELECT * FROM activity_providers where team_id = 862;
SELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT t.id, crm.id, crm.provider, ap.* FROM teams t
join crm_configurations crm on t.id = crm.team_id
join activity_providers ap on t.id = ap.team_id
where t.status = 'active' and ap.is_enabled = 1
and crm.provider = 'hubspot'
and ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',
'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');
SELECT * FROM teams where id = 1068;
SELECT * FROM crm_configurations where team_id = 1068;
SELECT * FROM activity_providers where team_id = 1068;
SELECT * FROM activities a
where crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')
and a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'
)
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by a.id desc;
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 = 1068 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262
SELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;
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 = 882 and sa.provider = 'hubspot';
select * from crm_layouts where crm_configuration_id = 834;
select * from crm_layout_entities where crm_layout_id = 2780;
select * from crm_fields where id IN (321153,321192,321193,321194);
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
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 = 1057 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8
SELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20
SELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10
SELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #
SELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;
select * from users where team_id = 51; # 7783
SELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130
select * from activity_searches where user_id = 7783;
select * from activity_search_filters where activity_search_id IN (32291, 32292);
SELECT asf.activity_search_id, asf.id, asf.value
FROM activity_search_filters asf
WHERE asf.filter = 'group_id'
AND asf.value IN (
SELECT CONCAT(
HEX(SUBSTR(uuid, 5, 4)), '-',
HEX(SUBSTR(uuid, 3, 2)), '-',
HEX(SUBSTR(uuid, 1, 2)), '-',
HEX(SUBSTR(uuid, 9, 2)), '-',
HEX(SUBSTR(uuid, 11))
)
FROM groups
WHERE deleted_at IS NOT NULL
);
SELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where provider = 'hubspot';
SELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133
SELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null
# [PASSWORD_DOTS]
select * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';
select
cp.*
# DISTINCT t.id
# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields
FROM crm_profiles cp
JOIN crm_configurations crm on crm.id = cp.crm_configuration_id
JOIN users u on u.id = cp.user_id
JOIN teams t ON t.id = crm.team_id
WHERE crm.provider = 'salesforce' and t.status = 'active'
and cp.archived_at IS NULL and u.deleted_at IS NULL
and t.id NOT IN (1093)
and t.id = 2
and cp.contact_fields IS NULL;
# and c.crm_provider_id = '003Uu00000ojD4NIAU';
SELECT * FROM users WHERE id = 26484;
SELECT * FROM crm_profiles WHERE user_id = 26484;
SELECT * FROM social_accounts WHERE sociable_id = 26484;
SELECT * FROM crm_configurations where provider = 'salesforce';
select * from users where id IN (10022, 10403);
select * from users where team_id IN (526);
select * from teams where id IN (526, 532);
select * from crm_configurations where id IN (500, 516);
select * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);
select * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';
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 = 526 and sa.provider = 'salesforce';
select * from team_settings where team_id IN (526, 532);
select * from users where id IN (22824);
select * from crm_profiles where crm_configuration_id IN (1026);
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 = 1093 and sa.provider = 'salesforce';
select * from teams where id = 1099;
select * from users where id = 29643
select * from activity_processing_states;
SELECT * FROM teams where name LIKE '%Fare%'; # 233
SELECT * FROM opportunities where crm_configuration_id = 215
# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'
;
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 = 1088 and sa.provider = 'hubspot';
SELECT * FROM teams order by updated_at DESC
SELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account
select * from crm_configurations where provider = 'pipedrive';
select * from teams where id = 957;
select * from crm_configurations where id = 957;
SELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743
SELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;
select * from users where team_id = 1; # 26726 - Gabriela Dureva
SELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific
select * from activities where user_id = 26726 order by id desc;
select * from contacts where crm_configuration_id = 1
and email IN ('[EMAIL]', '[EMAIL]'); # 2094416, 2093620
SELECT * FROM contacts WHERE id = 6284931;
SELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id
WHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;
select * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);
select * from crm_configurations where id = 1;
43801692-1aeb-32ce-acba-5b80a479701a
44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b
405975c0-b3d0-7aaa-821f-09d59cae6dd1
4caf848d-4bed-2299-b248-7788d41f9fca
49bedc3f-f196-eef3-89c3-dea6a3b4aa63
43420989-a09d-b8f8-9806-c8bbf7a02aac
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 = 1 and sa.provider = 'salesforce';
SELECT * FROM activities WHERE id = 75461988;
SELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;
select * from contacts where id = 17900517;
select * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id
where crm.provider != 'salesforce';
select * from users where id = 21047;
SELECT * FROM crm_configurations WHERE id = 892;
SELECT * FROM teams WHERE id = 942;
select * from opportunities where team_id = 942 order by updated_at desc;
select * from contacts where team_id = 942 order by updated_at desc;
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 = 942 and sa.provider = 'hubspot';
SELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430
SELECT * FROM crm_configurations WHERE id = 1;
SELECT * FROM teams WHERE crm_id = 1;
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 = 1 and sa.provider = 'salesforce';
select id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1
SELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430
select * from teams where id = 852;
select * from groups where id = 2286;
select * from sidekick_settings where team_id = 852;
select * from default_activity_types where team_id = 852;
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1 AND u.deleted_at IS NULL
AND u.crm_required = 1
AND u.team_id = 1
ORDER BY u.team_id;
SELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (
18481
);
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1
AND u.deleted_at IS NULL
AND u.crm_required = 1
# AND u.team_id = 1
AND p.id IS NULL -- Move this condition to WHERE clause
ORDER BY u.team_id;
SELECT * FROM opportunities WHERE id = 20002609;
select * from teams where id = 1122; # Velatir, 29953 - [EMAIL]
select * from crm_configurations where id = 1060;
select * from crm_layouts where crm_configuration_id = 1060;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;
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 = 1122 and sa.provider = 'hubspot';
select * from opportunities where team_id = 1122 order by updated_at desc;
select * from crm_field_data where object_type = 'contact';
SELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262
SELECT
CONCAT(u.id, CASE WHEN u.id = t.own...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85328
|
2921
|
45
|
2026-05-28T12:18:18.628145+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970698628_m2.jpg...
|
PhpStorm
|
faVsco.js – HubspotJournalPollingService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny...
|
[{"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":"#12121 on JY-20963-fix-import-on-deleted-entity, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.11569149,"height":0.025538707},"on_screen":true,"help_text":"Pull request #12121 exists for current branch JY-20963-fix-import-on-deleted-entity","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.8374335,"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":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"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 'TextRelayServiceTest'","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 'TextRelayServiceTest'","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":"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":"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":"AXStaticText","text":"12","depth":4,"bounds":{"left":0.37865692,"top":0.15003991,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"9","depth":4,"bounds":{"left":0.39029256,"top":0.15003991,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39993352,"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.40724733,"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\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.41589096,"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.4245346,"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.43550533,"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.44414893,"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.45279256,"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.4637633,"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.47473404,"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.5013298,"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.51230055,"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.7237367,"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}]...
|
-6565421303692608700
|
-30141114868883220
|
typing_pause
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny...
|
85327
|
NULL
|
NULL
|
NULL
|
|
85329
|
2920
|
42
|
2026-05-28T12:18:18.527128+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970698527_m1.jpg...
|
PhpStorm
|
faVsco.js – HubspotJournalPollingService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
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":"#12121 on JY-20963-fix-import-on-deleted-entity, menu","depth":5,"on_screen":true,"help_text":"Pull request #12121 exists for current branch JY-20963-fix-import-on-deleted-entity","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":"TextRelayServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","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":"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}]...
|
-3992895943309311970
|
-7046460962400697920
|
typing_pause
|
hybrid
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
PhpStormFileEditViewNavigateCodeRefactorRunToolsGitWindowHelpec2-user@ip-10-30-129-190:~screenpipe"884‹$0(ah]A100% <78• Thu 28 May 15:18:18181ec2-user@ip-10-30-140-...₴7DOCKER#_####_#####\\###||\#/V~'881DEV (-zsh)O [EMAIL] Linux 2023 (ECS Optimized)_/m/For documentation, visit [URL_WITH_CREDENTIALS] ~]$ exitlogoutConnection to jiminny-prod-ecsi closed.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ applukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ vprodWarning: Permanently added 'jiminny-prod-ecs1' (ED25519) to the list of known hosts.A newer release of "Amazon Linux" is available.Version 2023.10.20260330:Version 2023.11.20260406:Version 2023.11.20260413:Version2023.11.20260427:Version2023.11.20260505:Version 2023.11.20260509:Version 2023.11.20260511:Version 2023.11.20260514:Run "/usr/bin/dnf check-release-update" for full release and version update info#_~\ ####_\ #####\\###1\#/Amazon Linux 2023 (ECS Optimized)/m/'For documentation, visit [URL_WITH_CREDENTIALS] ~]$ client_loop: send disconnect: Broken pipeukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-relay) $I...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85327
|
2921
|
44
|
2026-05-28T12:18:17.070906+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970697070_m2.jpg...
|
PhpStorm
|
faVsco.js – HubspotJournalPollingService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
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
45
1
41
66
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993
SELECT * FROM users WHERE id = 25061;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 994;
SELECT * FROM crm_profiles WHERE user_id = 25061;
select * from crm_configurations where id = 834;
SELECT * FROM teams WHERE id = 882;
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 = 882 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;
SELECT * FROM contacts where crm_configuration_id = 834;
SELECT * FROM opportunities WHERE team_id = 933
# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');
AND id IN (8482561,18352941,19042734,19232139,19445140,19472541);
SELECT * FROM opportunity_contacts
WHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 485; #
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
select crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id
where crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')
# and l.converted_at IS NOT NULL
;
# [PASSWORD_DOTS]
SELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')
and opportunity_id IS NULL
order by id desc;
SELECT * FROM teams WHERE id = 604; # 598
SELECT * FROM activities WHERE id = 74410828; # [EMAIL]
SELECT * FROM accounts WHERE id = 20068382;
SELECT * FROM accounts WHERE id = 35186038;
SELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;
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 = 559 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;
select * from sidekick_settings where team_id = 781;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100
SELECT * FROM crm_layouts WHERE crm_configuration_id = 711;
SELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL
and is_internal = 0 and status = 'completed'
order by id desc;
SELECT * FROM crm_layout_entities
WHERE crm_layout_id IN (2352, 2353);
;
SELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;
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 = 556 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;
SELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;
select * from contacts
where crm_configuration_id = 530
and crm_provider_id = 872252;
select * from activities where crm_configuration_id = 530
and user_id = 14343 and type like '%softphone%'
and created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);
SELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t
JOIN crm_configurations c ON t.id = c.team_id
WHERE t.status = 'active';
SELECT * FROM teams where id = 1091;
SELECT * FROM crm_configurations where team_id = 1091;
SELECT * FROM activity_providers where team_id = 1091;
SELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT * FROM teams WHERE name LIKE '%Leadventure%';
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 = 1091 and sa.provider = 'salesforce';
SELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812
SELECT * FROM teams where id = 862;
SELECT * FROM crm_configurations where team_id = 862;
SELECT * FROM activity_providers where team_id = 862;
SELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT t.id, crm.id, crm.provider, ap.* FROM teams t
join crm_configurations crm on t.id = crm.team_id
join activity_providers ap on t.id = ap.team_id
where t.status = 'active' and ap.is_enabled = 1
and crm.provider = 'hubspot'
and ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',
'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');
SELECT * FROM teams where id = 1068;
SELECT * FROM crm_configurations where team_id = 1068;
SELECT * FROM activity_providers where team_id = 1068;
SELECT * FROM activities a
where crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')
and a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'
)
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by a.id desc;
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 = 1068 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262
SELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;
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 = 882 and sa.provider = 'hubspot';
select * from crm_layouts where crm_configuration_id = 834;
select * from crm_layout_entities where crm_layout_id = 2780;
select * from crm_fields where id IN (321153,321192,321193,321194);
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
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 = 1057 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8
SELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20
SELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10
SELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #
SELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;
select * from users where team_id = 51; # 7783
SELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130
select * from activity_searches where user_id = 7783;
select * from activity_search_filters where activity_search_id IN (32291, 32292);
SELECT asf.activity_search_id, asf.id, asf.value
FROM activity_search_filters asf
WHERE asf.filter = 'group_id'
AND asf.value IN (
SELECT CONCAT(
HEX(SUBSTR(uuid, 5, 4)), '-',
HEX(SUBSTR(uuid, 3, 2)), '-',
HEX(SUBSTR(uuid, 1, 2)), '-',
HEX(SUBSTR(uuid, 9, 2)), '-',
HEX(SUBSTR(uuid, 11))
)
FROM groups
WHERE deleted_at IS NOT NULL
);
SELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where provider = 'hubspot';
SELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133
SELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null
# [PASSWORD_DOTS]
select * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';
select
cp.*
# DISTINCT t.id
# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields
FROM crm_profiles cp
JOIN crm_configurations crm on crm.id = cp.crm_configuration_id
JOIN users u on u.id = cp.user_id
JOIN teams t ON t.id = crm.team_id
WHERE crm.provider = 'salesforce' and t.status = 'active'
and cp.archived_at IS NULL and u.deleted_at IS NULL
and t.id NOT IN (1093)
and t.id = 2
and cp.contact_fields IS NULL;
# and c.crm_provider_id = '003Uu00000ojD4NIAU';
SELECT * FROM users WHERE id = 26484;
SELECT * FROM crm_profiles WHERE user_id = 26484;
SELECT * FROM social_accounts WHERE sociable_id = 26484;
SELECT * FROM crm_configurations where provider = 'salesforce';
select * from users where id IN (10022, 10403);
select * from users where team_id IN (526);
select * from teams where id IN (526, 532);
select * from crm_configurations where id IN (500, 516);
select * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);
select * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';
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 = 526 and sa.provider = 'salesforce';
select * from team_settings where team_id IN (526, 532);
select * from users where id IN (22824);
select * from crm_profiles where crm_configuration_id IN (1026);
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 = 1093 and sa.provider = 'salesforce';
select * from teams where id = 1099;
select * from users where id = 29643
select * from activity_processing_states;
SELECT * FROM teams where name LIKE '%Fare%'; # 233
SELECT * FROM opportunities where crm_configuration_id = 215
# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'
;
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 = 1088 and sa.provider = 'hubspot';
SELECT * FROM teams order by updated_at DESC
SELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account
select * from crm_configurations where provider = 'pipedrive';
select * from teams where id = 957;
select * from crm_configurations where id = 957;
SELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743
SELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;
select * from users where team_id = 1; # 26726 - Gabriela Dureva
SELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific
select * from activities where user_id = 26726 order by id desc;
select * from contacts where crm_configuration_id = 1
and email IN ('[EMAIL]', '[EMAIL]'); # 2094416, 2093620
SELECT * FROM contacts WHERE id = 6284931;
SELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id
WHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;
select * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);
select * from crm_configurations where id = 1;
43801692-1aeb-32ce-acba-5b80a479701a
44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b
405975c0-b3d0-7aaa-821f-09d59cae6dd1
4caf848d-4bed-2299-b248-7788d41f9fca
49bedc3f-f196-eef3-89c3-dea6a3b4aa63
43420989-a09d-b8f8-9806-c8bbf7a02aac
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 = 1 and sa.provider = 'salesforce';
SELECT * FROM activities WHERE id = 75461988;
SELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;
select * from contacts where id = 17900517;
select * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id
where crm.provider != 'salesforce';
select * from users where id = 21047;
SELECT * FROM crm_configurations WHERE id = 892;
SELECT * FROM teams WHERE id = 942;
select * from opportunities where team_id = 942 order by updated_at desc;
select * from contacts where team_id = 942 order by updated_at desc;
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 = 942 and sa.provider = 'hubspot';
SELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430
SELECT * FROM crm_configurations WHERE id = 1;
SELECT * FROM teams WHERE crm_id = 1;
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 = 1 and sa.provider = 'salesforce';
select id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1
SELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430
select * from teams where id = 852;
select * from groups where id = 2286;
select * from sidekick_settings where team_id = 852;
select * from default_activity_types where team_id = 852;
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1 AND u.deleted_at IS NULL
AND u.crm_required = 1
AND u.team_id = 1
ORDER BY u.team_id;
SELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (
18481
);
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1
AND u.deleted_at IS NULL
AND u.crm_required = 1
# AND u.team_id = 1
AND p.id IS NULL -- Move this condition to WHERE clause
ORDER BY u.team_id;
SELECT * FROM opportunities WHERE id = 20002609;
select * from teams where id = 1122; # Velatir, 29953 - [EMAIL]
select * from crm_configurations where id = 1060;
select * from crm_layouts where crm_configuration_id = 1060;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;
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 = 1122 and sa.provider = 'hubspot';
select * from opportunities where team_id = 1122 order by updated_at desc;
select * from crm_field_data where object_type = 'contact';
SELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262
SELECT
CONCAT(u.id, CASE WHEN u.id = t.own...
|
[{"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":"#12121 on JY-20963-fix-import-on-deleted-entity, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.11569149,"height":0.025538707},"on_screen":true,"help_text":"Pull request #12121 exists for current branch JY-20963-fix-import-on-deleted-entity","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.8374335,"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":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"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 'TextRelayServiceTest'","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 'TextRelayServiceTest'","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":"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":"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":"AXStaticText","text":"12","depth":4,"bounds":{"left":0.37865692,"top":0.15003991,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"9","depth":4,"bounds":{"left":0.39029256,"top":0.15003991,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39993352,"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.40724733,"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\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.41589096,"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.4245346,"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.43550533,"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.44414893,"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.45279256,"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.4637633,"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.47473404,"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.5013298,"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.51230055,"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.7237367,"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":"45","depth":4,"bounds":{"left":0.6938165,"top":0.123703115,"width":0.010305851,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.70611703,"top":0.123703115,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"41","depth":4,"bounds":{"left":0.71542555,"top":0.123703115,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"66","depth":4,"bounds":{"left":0.72706115,"top":0.123703115,"width":0.010305851,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.7390292,"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.7463431,"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":"SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993\nSELECT * FROM users WHERE id = 25061;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 994;\nSELECT * FROM crm_profiles WHERE user_id = 25061;\n\nselect * from crm_configurations where id = 834;\nSELECT * FROM teams WHERE id = 882;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;\n\nSELECT * FROM contacts where crm_configuration_id = 834;\nSELECT * FROM opportunities WHERE team_id = 933\n# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');\nAND id IN (8482561,18352941,19042734,19232139,19445140,19472541);\nSELECT * FROM opportunity_contacts\nWHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 485; #\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nselect crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id\nwhere crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')\n# and l.converted_at IS NOT NULL\n;\n\n# ********************************************************************\nSELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')\nand opportunity_id IS NULL\norder by id desc;\n\nSELECT * FROM teams WHERE id = 604; # 598\nSELECT * FROM activities WHERE id = 74410828; # chelseaw@allvoices.co\nSELECT * FROM accounts WHERE id = 20068382;\nSELECT * FROM accounts WHERE id = 35186038;\n\nSELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 559 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;\nselect * from sidekick_settings where team_id = 781;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100\n\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 711;\nSELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL\nand is_internal = 0 and status = 'completed'\norder by id desc;\n\nSELECT * FROM crm_layout_entities\nWHERE crm_layout_id IN (2352, 2353);\n;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 556 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;\nSELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;\nselect * from contacts\nwhere crm_configuration_id = 530\nand crm_provider_id = 872252;\n\nselect * from activities where crm_configuration_id = 530\nand user_id = 14343 and type like '%softphone%'\nand created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);\n\n\nSELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t\nJOIN crm_configurations c ON t.id = c.team_id\nWHERE t.status = 'active';\n\nSELECT * FROM teams where id = 1091;\nSELECT * FROM crm_configurations where team_id = 1091;\nSELECT * FROM activity_providers where team_id = 1091;\nSELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT * FROM teams WHERE name LIKE '%Leadventure%';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1091 and sa.provider = 'salesforce';\n\nSELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812\nSELECT * FROM teams where id = 862;\nSELECT * FROM crm_configurations where team_id = 862;\nSELECT * FROM activity_providers where team_id = 862;\nSELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT t.id, crm.id, crm.provider, ap.* FROM teams t\njoin crm_configurations crm on t.id = crm.team_id\njoin activity_providers ap on t.id = ap.team_id\nwhere t.status = 'active' and ap.is_enabled = 1\nand crm.provider = 'hubspot'\nand ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',\n 'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');\n\nSELECT * FROM teams where id = 1068;\nSELECT * FROM crm_configurations where team_id = 1068;\nSELECT * FROM activity_providers where team_id = 1068;\n\nSELECT * FROM activities a\nwhere crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')\nand a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'\n )\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by a.id desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1068 and sa.provider = 'hubspot';\n\n# ********************************************************************\n# ********************************************************************\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262\nSELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\nselect * from crm_layouts where crm_configuration_id = 834;\nselect * from crm_layout_entities where crm_layout_id = 2780;\nselect * from crm_fields where id IN (321153,321192,321193,321194);\n\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1057 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8\n\nSELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20\n\nSELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10\n\nSELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #\n\nSELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;\nselect * from users where team_id = 51; # 7783\nSELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130\nselect * from activity_searches where user_id = 7783;\nselect * from activity_search_filters where activity_search_id IN (32291, 32292);\n\nSELECT asf.activity_search_id, asf.id, asf.value\nFROM activity_search_filters asf\nWHERE asf.filter = 'group_id'\nAND asf.value IN (\n SELECT CONCAT(\n HEX(SUBSTR(uuid, 5, 4)), '-',\n HEX(SUBSTR(uuid, 3, 2)), '-',\n HEX(SUBSTR(uuid, 1, 2)), '-',\n HEX(SUBSTR(uuid, 9, 2)), '-',\n HEX(SUBSTR(uuid, 11))\n )\n FROM groups\n WHERE deleted_at IS NOT NULL\n);\n\nSELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th\n# ********************************************************************\nSELECT * FROM crm_configurations where provider = 'hubspot';\nSELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133\nSELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null\n# ********************************************************************\n\nselect * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';\nselect\n cp.*\n# DISTINCT t.id\n# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields\nFROM crm_profiles cp\nJOIN crm_configurations crm on crm.id = cp.crm_configuration_id\nJOIN users u on u.id = cp.user_id\nJOIN teams t ON t.id = crm.team_id\nWHERE crm.provider = 'salesforce' and t.status = 'active'\n and cp.archived_at IS NULL and u.deleted_at IS NULL\n and t.id NOT IN (1093)\n and t.id = 2\n and cp.contact_fields IS NULL;\n# and c.crm_provider_id = '003Uu00000ojD4NIAU';\n\nSELECT * FROM users WHERE id = 26484;\nSELECT * FROM crm_profiles WHERE user_id = 26484;\nSELECT * FROM social_accounts WHERE sociable_id = 26484;\nSELECT * FROM crm_configurations where provider = 'salesforce';\nselect * from users where id IN (10022, 10403);\nselect * from users where team_id IN (526);\nselect * from teams where id IN (526, 532);\nselect * from crm_configurations where id IN (500, 516);\nselect * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);\nselect * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 526 and sa.provider = 'salesforce';\nselect * from team_settings where team_id IN (526, 532);\n\nselect * from users where id IN (22824);\nselect * from crm_profiles where crm_configuration_id IN (1026);\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1093 and sa.provider = 'salesforce';\n\nselect * from teams where id = 1099;\nselect * from users where id = 29643\n\nselect * from activity_processing_states;\n\nSELECT * FROM teams where name LIKE '%Fare%'; # 233\nSELECT * FROM opportunities where crm_configuration_id = 215\n# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1088 and sa.provider = 'hubspot';\n\nSELECT * FROM teams order by updated_at DESC\nSELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account\n\nselect * from crm_configurations where provider = 'pipedrive';\n\nselect * from teams where id = 957;\nselect * from crm_configurations where id = 957;\n\nSELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743\nSELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;\n\nselect * from users where team_id = 1; # 26726 - Gabriela Dureva\nSELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific\nselect * from activities where user_id = 26726 order by id desc;\nselect * from contacts where crm_configuration_id = 1\nand email IN ('charlotte.ward@prolific.com', 'frankie.bryant@prolific.com'); # 2094416, 2093620\nSELECT * FROM contacts WHERE id = 6284931;\n\nSELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id\nWHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;\n\nselect * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);\nselect * from crm_configurations where id = 1;\n\n43801692-1aeb-32ce-acba-5b80a479701a\n44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b\n405975c0-b3d0-7aaa-821f-09d59cae6dd1\n4caf848d-4bed-2299-b248-7788d41f9fca\n49bedc3f-f196-eef3-89c3-dea6a3b4aa63\n43420989-a09d-b8f8-9806-c8bbf7a02aac\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nSELECT * FROM activities WHERE id = 75461988;\n\nSELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;\n\nselect * from contacts where id = 17900517;\n\nselect * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id\nwhere crm.provider != 'salesforce';\n\nselect * from users where id = 21047;\nSELECT * FROM crm_configurations WHERE id = 892;\nSELECT * FROM teams WHERE id = 942;\nselect * from opportunities where team_id = 942 order by updated_at desc;\nselect * from contacts where team_id = 942 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 942 and sa.provider = 'hubspot';\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430\nSELECT * FROM crm_configurations WHERE id = 1;\nSELECT * FROM teams WHERE crm_id = 1;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nselect id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1\nSELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430\n\nselect * from teams where id = 852;\nselect * from groups where id = 2286;\nselect * from sidekick_settings where team_id = 852;\nselect * from default_activity_types where team_id = 852;\n\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1 AND u.deleted_at IS NULL\nAND u.crm_required = 1\nAND u.team_id = 1\nORDER BY u.team_id;\n\nSELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (\n18481\n );\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1\n AND u.deleted_at IS NULL\n AND u.crm_required = 1\n# AND u.team_id = 1\n AND p.id IS NULL -- Move this condition to WHERE clause\nORDER BY u.team_id;\n\nSELECT * FROM opportunities WHERE id = 20002609;\nselect * from teams where id = 1122; # Velatir, 29953 - christian@velatir.com\nselect * from crm_configurations where id = 1060;\nselect * from crm_layouts where crm_configuration_id = 1060;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1122 and sa.provider = 'hubspot';\nselect * from opportunities where team_id = 1122 order by updated_at desc;\n\nselect * from crm_field_data where object_type = 'contact';\n\nSELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 248 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS\nSELECT * FROM users where id = 24115;\nSELECT * FROM accounts where id = 4002896;\nSELECT * FROM teams WHERE name LIKE '%adswerve%';\nSELECT * FROM opportunities where crm_configuration_id = 230 AND crm_provider_id IN (\"0069N000003GIQ9QAO\",\"0061r000019yGP9AAM\",\"0066900001S2KWlAAN\",\"0066900001TDpj2AAD\",\"0066900001b8uEwAAI\",\"0069N000001rQi0QAE\",\"006QF00000KD40mYAD\",\"006QF00000LzpRJYAZ\",\"0069N000002uomtQAA\",\"0069N000002xlMLQAY\",\"0066900001NV6ubAAD\",\"0061r00001HJp45AAD\",\"006QF00000uTlUoYAK\",\"006QF00000v0bZqYAI\");\nSELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203\n\nSELECT u.id, u.email, ac.name, a.* FROM activities a\nJOIN users u ON a.user_id = u.id\nJOIN accounts ac ON a.account_id = ac.id\nWHERE\nuuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or\nuuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or\nuuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;\n\nselect * from users where id = 5825;\nSELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;\n\nselect * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;\n19594, 862\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 862 and sa.provider = 'salesforce';\n\nselect * from automated_reports where id = 36;\nselect ar.frequency, r.*, ar.* from automated_report_results r\njoin automated_reports ar on r.report_id = ar.id\nwhere ar.frequency != 'one_off';\n\nselect s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;\nselect * from nudges n where n.activity_search_id\n\nselect * from teams where created_at > '2026-03-09';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;\n\nselect * from users where team_id = 1 and name like '%Lukas%'; # 7160\n\nSELECT * FROM teams WHERE id = 575;\nselect * from opportunities where team_id = 575;\nSELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,\nselect * from opportunities where team_id = 1126;\nSELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,\nselect * from opportunities where team_id = 1125;\nselect * from contacts c\nwhere c.team_id = 882;\n\nSELECT * FROM activities WHERE id = 76822967;\nSELECT * FROM crm_profiles WHERE user_id = 15440;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 555;\nSELECT * FROM crm_configurations WHERE id = 555;\nSELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 581 and sa.provider = 'salesforce';\n\nSELECT * FROM automated_report_results order by id desc;\n\nselect * from features;\nselect * from team_features where feature_id = 40;\n\nselect * from teams where id = 556;\n\nselect * from automated_reports;\nwhere id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , [\"pdf\",\"podcast\"]\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\nselect * from automated_report_results order by id desc;\nSELECT * FROM automated_report_results WHERE id = 1919;\n\nselect * from automated_report_results WHERE report_id = 54;\n\nselect * from opportunities where id = 7594349;\n\nSELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067 - jiminnyintegration@lesmills.com\nselect * from playbooks where team_id = 711; # event 226147\nSELECT * FROM playbook_categories WHERE playbook_id = 5515;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event';\nSELECT * FROM crm_fields WHERE id = 226147;\nSELECT * FROM crm_field_values WHERE crm_field_id = 226147;\n\nSELECT * FROM crm_configurations WHERE id = 692;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 711 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles cp JOIN users u on u.id = cp.user_id WHERE u.team_id = 711;\n\nselect * from leads;\n\nselect * from calendars;\n\nSELECT\n t.id AS team_id,\n t.name,\n LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1)) AS calendar_domain\nFROM teams t\nJOIN users u ON u.team_id = t.id\nJOIN calendars c ON c.user_id = u.id AND c.status = 'active' AND c.calendar_provider_id LIKE '%@%'\nLEFT JOIN team_domains td\n ON td.team_id = t.id\n AND td.deleted_at IS NULL\n AND td.domain = LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1))\nGROUP BY t.id, t.name, calendar_domain\nORDER BY t.name, calendar_domain;\n\nselect * from users u join calendars c on c.user_id = u.id\nwhere u.team_id = 882;\n\n\nselect * from activities where id = 74049485; # team 563 crm 537\nselect * from activities where id = 73272382; # team 563 crm 537\nselect * from activities where id = 64400389; # team 563 crm 537\nselect * from activities where id = 58081273; # team 563 crm 537\nselect * from activities where id = 54520297; # team 563 crm 537\nselect * from participants where activity_id = 58081273;\n\nselect * from activities where crm_configuration_id = 537 and provider = 'aircall'\nand account_id = 19003658 order by updated_at desc;\n\nselect * from contacts where crm_configuration_id = 537 and id = 35957759;\nselect * from accounts where crm_configuration_id = 537 and id = 19003658;\n\nselect * from automated_report_results where id = 1976;\nselect * from automated_reports where id = 583;\nselect * from activity_searches where id = 87714;\nselect * from activity_search_filters where activity_search_id = 87714;\n\nSELECT * FROM activities WHERE uuid_to_bin('8827f672-202d-4162-9d04-73ff5f0566a9') = uuid\nor uuid_to_bin('47842446-af51-4bcb-854f-cc6560290101') = uuid;\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot';\nselect * from rate_limits;\n\nselect * from automated_report_results where media_type = 'pdf' and status = 2\nand id IN (18, 1872);\nselect * from automated_reports where id = 54;\nSELECT * FROM users WHERE id IN (24623,29443,29613);\n\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\n\nselect * from text_relays where created_at > '2026-05-01';\nand id IN (32415, 32416);\n# and id = 32412;\n\nselect * from users where team_id = 2 and email like '%scott%' and id = 29510;\n\nSELECT * FROM activities WHERE uuid_to_bin('67cebfc2-ed56-44a2-8c68-7a0286ed8618') = uuid; # 79763436\n\nSELECT email_provider_id, COUNT(*) as count, GROUP_CONCAT(id) as ids, GROUP_CONCAT(status) as statuses\nFROM text_relays\nWHERE email_provider_id IN ('19e2027868a64b42', '19e2033ed8ea6b10')\nGROUP BY email_provider_id;\nSELECT id, status, telephony_provider_id, created_at\nFROM activities\nWHERE id IN (80028719, 80028846);\nSELECT id, status, code, email_sent_at, created_at, updated_at\nFROM text_relays\nWHERE id IN (32415, 32416);\nSELECT id, status, code, sender, recipient, created_at\nFROM text_relays\nWHERE sender LIKE '%mario.georgiev%' OR sender LIKE '%stoyan.tomov%'\nORDER BY created_at DESC\nLIMIT 10;\n\nSELECT id, uuid, status, code, sender, recipient, created_at, updated_at\nFROM text_relays\nWHERE uuid = uuid_to_bin('0626141c-27a6-4d8c-aff8-7c8020a2c656');\n\n# ***************\nSELECT DISTINCT u.id, u.email, u.name, u.softphone_number, COUNT(a.id) as sms_count\nFROM users u\nINNER JOIN activities a ON u.id = .user_id\nWHERE a.type LIKE 'sms%'\nAND a.created_at > DATE_SUB(NOW(), INTERVAL 30 DAY)\nGROUP BY u.id, u.email, u.name, u.softphone_number\nORDER BY sms_count DESC;\n\nselect * from teams where id = 1;\n\nselect * from roles;\n\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1117 and sa.provider = 'hubspot';\nSELECT * FROM activities WHERE uuid_to_bin('8024fffb-2df7-4017-91f4-d9f896050248') = uuid; # 79933459 YES\nSELECT * FROM activities WHERE uuid_to_bin('25529043-8094-4781-927f-4f4da2a8185c') = uuid; # 80186192 NO\nSELECT * FROM crm_configurations WHERE id = 1053;\nSELECT * FROM teams WHERE id = 1117;\nselect * from users where id = 30249;\nselect * from playbooks where id = 5473;\nselect * from playbook_categories where id = 43783;\nselect * from playbook_categories where playbook_id = 5473;\nselect * from crm_fields where id = 659242;\nselect * from crm_field_values where crm_field_id = 659242;\n\nSELECT * FROM crm_field_data fd\n# JOIN crm_fields f ON fd.crm_field_id = f.id\n# JOIN activities a ON fd.activity_id = a.id\nWHERE activity_id = 79933459\n# AND f.crm_provider_id = 'hs_activity_type';\n\n\nSELECT * FROM activity_messages;\nselect * from text_relays where created_at > '2026-05-01';\nselect * from activities where user_id IN (7160, 18608) and created_at > '2026-05-22' order by id desc;\nselect * from users where team_id = 1 and id IN (18608, 13934, 7160);\nselect * from activities where user_id = 7160 order by id desc limit 10;\n\nselect * from accounts where team_id = 1 and name = 'Column5';\n\nselect * from users where name like '%Subra%'; # 31054, 1117\nselect * from teams where id = 1117;\nselect * from activity_searches where user_id = 31054;\nselect * from activity_search_filters where activity_search_id IN (88882, 88902);","depth":4,"on_screen":true,"value":"SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993\nSELECT * FROM users WHERE id = 25061;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 994;\nSELECT * FROM crm_profiles WHERE user_id = 25061;\n\nselect * from crm_configurations where id = 834;\nSELECT * FROM teams WHERE id = 882;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;\n\nSELECT * FROM contacts where crm_configuration_id = 834;\nSELECT * FROM opportunities WHERE team_id = 933\n# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');\nAND id IN (8482561,18352941,19042734,19232139,19445140,19472541);\nSELECT * FROM opportunity_contacts\nWHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 485; #\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nselect crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id\nwhere crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')\n# and l.converted_at IS NOT NULL\n;\n\n# ********************************************************************\nSELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')\nand opportunity_id IS NULL\norder by id desc;\n\nSELECT * FROM teams WHERE id = 604; # 598\nSELECT * FROM activities WHERE id = 74410828; # chelseaw@allvoices.co\nSELECT * FROM accounts WHERE id = 20068382;\nSELECT * FROM accounts WHERE id = 35186038;\n\nSELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 559 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;\nselect * from sidekick_settings where team_id = 781;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100\n\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 711;\nSELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL\nand is_internal = 0 and status = 'completed'\norder by id desc;\n\nSELECT * FROM crm_layout_entities\nWHERE crm_layout_id IN (2352, 2353);\n;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 556 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;\nSELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;\nselect * from contacts\nwhere crm_configuration_id = 530\nand crm_provider_id = 872252;\n\nselect * from activities where crm_configuration_id = 530\nand user_id = 14343 and type like '%softphone%'\nand created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);\n\n\nSELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t\nJOIN crm_configurations c ON t.id = c.team_id\nWHERE t.status = 'active';\n\nSELECT * FROM teams where id = 1091;\nSELECT * FROM crm_configurations where team_id = 1091;\nSELECT * FROM activity_providers where team_id = 1091;\nSELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT * FROM teams WHERE name LIKE '%Leadventure%';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1091 and sa.provider = 'salesforce';\n\nSELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812\nSELECT * FROM teams where id = 862;\nSELECT * FROM crm_configurations where team_id = 862;\nSELECT * FROM activity_providers where team_id = 862;\nSELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT t.id, crm.id, crm.provider, ap.* FROM teams t\njoin crm_configurations crm on t.id = crm.team_id\njoin activity_providers ap on t.id = ap.team_id\nwhere t.status = 'active' and ap.is_enabled = 1\nand crm.provider = 'hubspot'\nand ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',\n 'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');\n\nSELECT * FROM teams where id = 1068;\nSELECT * FROM crm_configurations where team_id = 1068;\nSELECT * FROM activity_providers where team_id = 1068;\n\nSELECT * FROM activities a\nwhere crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')\nand a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'\n )\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by a.id desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1068 and sa.provider = 'hubspot';\n\n# ********************************************************************\n# ********************************************************************\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262\nSELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\nselect * from crm_layouts where crm_configuration_id = 834;\nselect * from crm_layout_entities where crm_layout_id = 2780;\nselect * from crm_fields where id IN (321153,321192,321193,321194);\n\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1057 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8\n\nSELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20\n\nSELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10\n\nSELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #\n\nSELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;\nselect * from users where team_id = 51; # 7783\nSELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130\nselect * from activity_searches where user_id = 7783;\nselect * from activity_search_filters where activity_search_id IN (32291, 32292);\n\nSELECT asf.activity_search_id, asf.id, asf.value\nFROM activity_search_filters asf\nWHERE asf.filter = 'group_id'\nAND asf.value IN (\n SELECT CONCAT(\n HEX(SUBSTR(uuid, 5, 4)), '-',\n HEX(SUBSTR(uuid, 3, 2)), '-',\n HEX(SUBSTR(uuid, 1, 2)), '-',\n HEX(SUBSTR(uuid, 9, 2)), '-',\n HEX(SUBSTR(uuid, 11))\n )\n FROM groups\n WHERE deleted_at IS NOT NULL\n);\n\nSELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th\n# ********************************************************************\nSELECT * FROM crm_configurations where provider = 'hubspot';\nSELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133\nSELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null\n# ********************************************************************\n\nselect * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';\nselect\n cp.*\n# DISTINCT t.id\n# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields\nFROM crm_profiles cp\nJOIN crm_configurations crm on crm.id = cp.crm_configuration_id\nJOIN users u on u.id = cp.user_id\nJOIN teams t ON t.id = crm.team_id\nWHERE crm.provider = 'salesforce' and t.status = 'active'\n and cp.archived_at IS NULL and u.deleted_at IS NULL\n and t.id NOT IN (1093)\n and t.id = 2\n and cp.contact_fields IS NULL;\n# and c.crm_provider_id = '003Uu00000ojD4NIAU';\n\nSELECT * FROM users WHERE id = 26484;\nSELECT * FROM crm_profiles WHERE user_id = 26484;\nSELECT * FROM social_accounts WHERE sociable_id = 26484;\nSELECT * FROM crm_configurations where provider = 'salesforce';\nselect * from users where id IN (10022, 10403);\nselect * from users where team_id IN (526);\nselect * from teams where id IN (526, 532);\nselect * from crm_configurations where id IN (500, 516);\nselect * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);\nselect * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 526 and sa.provider = 'salesforce';\nselect * from team_settings where team_id IN (526, 532);\n\nselect * from users where id IN (22824);\nselect * from crm_profiles where crm_configuration_id IN (1026);\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1093 and sa.provider = 'salesforce';\n\nselect * from teams where id = 1099;\nselect * from users where id = 29643\n\nselect * from activity_processing_states;\n\nSELECT * FROM teams where name LIKE '%Fare%'; # 233\nSELECT * FROM opportunities where crm_configuration_id = 215\n# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1088 and sa.provider = 'hubspot';\n\nSELECT * FROM teams order by updated_at DESC\nSELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account\n\nselect * from crm_configurations where provider = 'pipedrive';\n\nselect * from teams where id = 957;\nselect * from crm_configurations where id = 957;\n\nSELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743\nSELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;\n\nselect * from users where team_id = 1; # 26726 - Gabriela Dureva\nSELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific\nselect * from activities where user_id = 26726 order by id desc;\nselect * from contacts where crm_configuration_id = 1\nand email IN ('charlotte.ward@prolific.com', 'frankie.bryant@prolific.com'); # 2094416, 2093620\nSELECT * FROM contacts WHERE id = 6284931;\n\nSELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id\nWHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;\n\nselect * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);\nselect * from crm_configurations where id = 1;\n\n43801692-1aeb-32ce-acba-5b80a479701a\n44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b\n405975c0-b3d0-7aaa-821f-09d59cae6dd1\n4caf848d-4bed-2299-b248-7788d41f9fca\n49bedc3f-f196-eef3-89c3-dea6a3b4aa63\n43420989-a09d-b8f8-9806-c8bbf7a02aac\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nSELECT * FROM activities WHERE id = 75461988;\n\nSELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;\n\nselect * from contacts where id = 17900517;\n\nselect * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id\nwhere crm.provider != 'salesforce';\n\nselect * from users where id = 21047;\nSELECT * FROM crm_configurations WHERE id = 892;\nSELECT * FROM teams WHERE id = 942;\nselect * from opportunities where team_id = 942 order by updated_at desc;\nselect * from contacts where team_id = 942 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 942 and sa.provider = 'hubspot';\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430\nSELECT * FROM crm_configurations WHERE id = 1;\nSELECT * FROM teams WHERE crm_id = 1;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nselect id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1\nSELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430\n\nselect * from teams where id = 852;\nselect * from groups where id = 2286;\nselect * from sidekick_settings where team_id = 852;\nselect * from default_activity_types where team_id = 852;\n\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1 AND u.deleted_at IS NULL\nAND u.crm_required = 1\nAND u.team_id = 1\nORDER BY u.team_id;\n\nSELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (\n18481\n );\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1\n AND u.deleted_at IS NULL\n AND u.crm_required = 1\n# AND u.team_id = 1\n AND p.id IS NULL -- Move this condition to WHERE clause\nORDER BY u.team_id;\n\nSELECT * FROM opportunities WHERE id = 20002609;\nselect * from teams where id = 1122; # Velatir, 29953 - christian@velatir.com\nselect * from crm_configurations where id = 1060;\nselect * from crm_layouts where crm_configuration_id = 1060;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1122 and sa.provider = 'hubspot';\nselect * from opportunities where team_id = 1122 order by updated_at desc;\n\nselect * from crm_field_data where object_type = 'contact';\n\nSELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 248 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS\nSELECT * FROM users where id = 24115;\nSELECT * FROM accounts where id = 4002896;\nSELECT * FROM teams WHERE name LIKE '%adswerve%';\nSELECT * FROM opportunities where crm_configuration_id = 230 AND crm_provider_id IN (\"0069N000003GIQ9QAO\",\"0061r000019yGP9AAM\",\"0066900001S2KWlAAN\",\"0066900001TDpj2AAD\",\"0066900001b8uEwAAI\",\"0069N000001rQi0QAE\",\"006QF00000KD40mYAD\",\"006QF00000LzpRJYAZ\",\"0069N000002uomtQAA\",\"0069N000002xlMLQAY\",\"0066900001NV6ubAAD\",\"0061r00001HJp45AAD\",\"006QF00000uTlUoYAK\",\"006QF00000v0bZqYAI\");\nSELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203\n\nSELECT u.id, u.email, ac.name, a.* FROM activities a\nJOIN users u ON a.user_id = u.id\nJOIN accounts ac ON a.account_id = ac.id\nWHERE\nuuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or\nuuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or\nuuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;\n\nselect * from users where id = 5825;\nSELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;\n\nselect * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;\n19594, 862\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 862 and sa.provider = 'salesforce';\n\nselect * from automated_reports where id = 36;\nselect ar.frequency, r.*, ar.* from automated_report_results r\njoin automated_reports ar on r.report_id = ar.id\nwhere ar.frequency != 'one_off';\n\nselect s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;\nselect * from nudges n where n.activity_search_id\n\nselect * from teams where created_at > '2026-03-09';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;\n\nselect * from users where team_id = 1 and name like '%Lukas%'; # 7160\n\nSELECT * FROM teams WHERE id = 575;\nselect * from opportunities where team_id = 575;\nSELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,\nselect * from opportunities where team_id = 1126;\nSELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,\nselect * from opportunities where team_id = 1125;\nselect * from contacts c\nwhere c.team_id = 882;\n\nSELECT * FROM activities WHERE id = 76822967;\nSELECT * FROM crm_profiles WHERE user_id = 15440;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 555;\nSELECT * FROM crm_configurations WHERE id = 555;\nSELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 581 and sa.provider = 'salesforce';\n\nSELECT * FROM automated_report_results order by id desc;\n\nselect * from features;\nselect * from team_features where feature_id = 40;\n\nselect * from teams where id = 556;\n\nselect * from automated_reports;\nwhere id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , [\"pdf\",\"podcast\"]\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\nselect * from automated_report_results order by id desc;\nSELECT * FROM automated_report_results WHERE id = 1919;\n\nselect * from automated_report_results WHERE report_id = 54;\n\nselect * from opportunities where id = 7594349;\n\nSELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067 - jiminnyintegration@lesmills.com\nselect * from playbooks where team_id = 711; # event 226147\nSELECT * FROM playbook_categories WHERE playbook_id = 5515;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event';\nSELECT * FROM crm_fields WHERE id = 226147;\nSELECT * FROM crm_field_values WHERE crm_field_id = 226147;\n\nSELECT * FROM crm_configurations WHERE id = 692;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 711 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles cp JOIN users u on u.id = cp.user_id WHERE u.team_id = 711;\n\nselect * from leads;\n\nselect * from calendars;\n\nSELECT\n t.id AS team_id,\n t.name,\n LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1)) AS calendar_domain\nFROM teams t\nJOIN users u ON u.team_id = t.id\nJOIN calendars c ON c.user_id = u.id AND c.status = 'active' AND c.calendar_provider_id LIKE '%@%'\nLEFT JOIN team_domains td\n ON td.team_id = t.id\n AND td.deleted_at IS NULL\n AND td.domain = LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1))\nGROUP BY t.id, t.name, calendar_domain\nORDER BY t.name, calendar_domain;\n\nselect * from users u join calendars c on c.user_id = u.id\nwhere u.team_id = 882;\n\n\nselect * from activities where id = 74049485; # team 563 crm 537\nselect * from activities where id = 73272382; # team 563 crm 537\nselect * from activities where id = 64400389; # team 563 crm 537\nselect * from activities where id = 58081273; # team 563 crm 537\nselect * from activities where id = 54520297; # team 563 crm 537\nselect * from participants where activity_id = 58081273;\n\nselect * from activities where crm_configuration_id = 537 and provider = 'aircall'\nand account_id = 19003658 order by updated_at desc;\n\nselect * from contacts where crm_configuration_id = 537 and id = 35957759;\nselect * from accounts where crm_configuration_id = 537 and id = 19003658;\n\nselect * from automated_report_results where id = 1976;\nselect * from automated_reports where id = 583;\nselect * from activity_searches where id = 87714;\nselect * from activity_search_filters where activity_search_id = 87714;\n\nSELECT * FROM activities WHERE uuid_to_bin('8827f672-202d-4162-9d04-73ff5f0566a9') = uuid\nor uuid_to_bin('47842446-af51-4bcb-854f-cc6560290101') = uuid;\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot';\nselect * from rate_limits;\n\nselect * from automated_report_results where media_type = 'pdf' and status = 2\nand id IN (18, 1872);\nselect * from automated_reports where id = 54;\nSELECT * FROM users WHERE id IN (24623,29443,29613);\n\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\n\nselect * from text_relays where created_at > '2026-05-01';\nand id IN (32415, 32416);\n# and id = 32412;\n\nselect * from users where team_id = 2 and email like '%scott%' and id = 29510;\n\nSELECT * FROM activities WHERE uuid_to_bin('67cebfc2-ed56-44a2-8c68-7a0286ed8618') = uuid; # 79763436\n\nSELECT email_provider_id, COUNT(*) as count, GROUP_CONCAT(id) as ids, GROUP_CONCAT(status) as statuses\nFROM text_relays\nWHERE email_provider_id IN ('19e2027868a64b42', '19e2033ed8ea6b10')\nGROUP BY email_provider_id;\nSELECT id, status, telephony_provider_id, created_at\nFROM activities\nWHERE id IN (80028719, 80028846);\nSELECT id, status, code, email_sent_at, created_at, updated_at\nFROM text_relays\nWHERE id IN (32415, 32416);\nSELECT id, status, code, sender, recipient, created_at\nFROM text_relays\nWHERE sender LIKE '%mario.georgiev%' OR sender LIKE '%stoyan.tomov%'\nORDER BY created_at DESC\nLIMIT 10;\n\nSELECT id, uuid, status, code, sender, recipient, created_at, updated_at\nFROM text_relays\nWHERE uuid = uuid_to_bin('0626141c-27a6-4d8c-aff8-7c8020a2c656');\n\n# ***************\nSELECT DISTINCT u.id, u.email, u.name, u.softphone_number, COUNT(a.id) as sms_count\nFROM users u\nINNER JOIN activities a ON u.id = .user_id\nWHERE a.type LIKE 'sms%'\nAND a.created_at > DATE_SUB(NOW(), INTERVAL 30 DAY)\nGROUP BY u.id, u.email, u.name, u.softphone_number\nORDER BY sms_count DESC;\n\nselect * from teams where id = 1;\n\nselect * from roles;\n\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1117 and sa.provider = 'hubspot';\nSELECT * FROM activities WHERE uuid_to_bin('8024fffb-2df7-4017-91f4-d9f896050248') = uuid; # 79933459 YES\nSELECT * FROM activities WHERE uuid_to_bin('25529043-8094-4781-927f-4f4da2a8185c') = uuid; # 80186192 NO\nSELECT * FROM crm_configurations WHERE id = 1053;\nSELECT * FROM teams WHERE id = 1117;\nselect * from users where id = 30249;\nselect * from playbooks where id = 5473;\nselect * from playbook_categories where id = 43783;\nselect * from playbook_categories where playbook_id = 5473;\nselect * from crm_fields where id = 659242;\nselect * from crm_field_values where crm_field_id = 659242;\n\nSELECT * FROM crm_field_data fd\n# JOIN crm_fields f ON fd.crm_field_id = f.id\n# JOIN activities a ON fd.activity_id = a.id\nWHERE activity_id = 79933459\n# AND f.crm_provider_id = 'hs_activity_type';\n\n\nSELECT * FROM activity_messages;\nselect * from text_relays where created_at > '2026-05-01';\nselect * from activities where user_id IN (7160, 18608) and created_at > '2026-05-22' order by id desc;\nselect * from users where team_id = 1 and id IN (18608, 13934, 7160);\nselect * from activities where user_id = 7160 order by id desc limit 10;\n\nselect * from accounts where team_id = 1 and name = 'Column5';\n\nselect * from users where name like '%Subra%'; # 31054, 1117\nselect * from teams where id = 1117;\nselect * from activity_searches where user_id = 31054;\nselect * from activity_search_filters where activity_search_id IN (88882, 88902);","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":"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}]...
|
2526630547225287665
|
1137771414947247693
|
typing_pause
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
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
45
1
41
66
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993
SELECT * FROM users WHERE id = 25061;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 994;
SELECT * FROM crm_profiles WHERE user_id = 25061;
select * from crm_configurations where id = 834;
SELECT * FROM teams WHERE id = 882;
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 = 882 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;
SELECT * FROM contacts where crm_configuration_id = 834;
SELECT * FROM opportunities WHERE team_id = 933
# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');
AND id IN (8482561,18352941,19042734,19232139,19445140,19472541);
SELECT * FROM opportunity_contacts
WHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 485; #
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
select crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id
where crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')
# and l.converted_at IS NOT NULL
;
# [PASSWORD_DOTS]
SELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')
and opportunity_id IS NULL
order by id desc;
SELECT * FROM teams WHERE id = 604; # 598
SELECT * FROM activities WHERE id = 74410828; # [EMAIL]
SELECT * FROM accounts WHERE id = 20068382;
SELECT * FROM accounts WHERE id = 35186038;
SELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;
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 = 559 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;
select * from sidekick_settings where team_id = 781;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100
SELECT * FROM crm_layouts WHERE crm_configuration_id = 711;
SELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL
and is_internal = 0 and status = 'completed'
order by id desc;
SELECT * FROM crm_layout_entities
WHERE crm_layout_id IN (2352, 2353);
;
SELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;
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 = 556 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;
SELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;
select * from contacts
where crm_configuration_id = 530
and crm_provider_id = 872252;
select * from activities where crm_configuration_id = 530
and user_id = 14343 and type like '%softphone%'
and created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);
SELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t
JOIN crm_configurations c ON t.id = c.team_id
WHERE t.status = 'active';
SELECT * FROM teams where id = 1091;
SELECT * FROM crm_configurations where team_id = 1091;
SELECT * FROM activity_providers where team_id = 1091;
SELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT * FROM teams WHERE name LIKE '%Leadventure%';
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 = 1091 and sa.provider = 'salesforce';
SELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812
SELECT * FROM teams where id = 862;
SELECT * FROM crm_configurations where team_id = 862;
SELECT * FROM activity_providers where team_id = 862;
SELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT t.id, crm.id, crm.provider, ap.* FROM teams t
join crm_configurations crm on t.id = crm.team_id
join activity_providers ap on t.id = ap.team_id
where t.status = 'active' and ap.is_enabled = 1
and crm.provider = 'hubspot'
and ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',
'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');
SELECT * FROM teams where id = 1068;
SELECT * FROM crm_configurations where team_id = 1068;
SELECT * FROM activity_providers where team_id = 1068;
SELECT * FROM activities a
where crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')
and a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'
)
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by a.id desc;
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 = 1068 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262
SELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;
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 = 882 and sa.provider = 'hubspot';
select * from crm_layouts where crm_configuration_id = 834;
select * from crm_layout_entities where crm_layout_id = 2780;
select * from crm_fields where id IN (321153,321192,321193,321194);
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
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 = 1057 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8
SELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20
SELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10
SELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #
SELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;
select * from users where team_id = 51; # 7783
SELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130
select * from activity_searches where user_id = 7783;
select * from activity_search_filters where activity_search_id IN (32291, 32292);
SELECT asf.activity_search_id, asf.id, asf.value
FROM activity_search_filters asf
WHERE asf.filter = 'group_id'
AND asf.value IN (
SELECT CONCAT(
HEX(SUBSTR(uuid, 5, 4)), '-',
HEX(SUBSTR(uuid, 3, 2)), '-',
HEX(SUBSTR(uuid, 1, 2)), '-',
HEX(SUBSTR(uuid, 9, 2)), '-',
HEX(SUBSTR(uuid, 11))
)
FROM groups
WHERE deleted_at IS NOT NULL
);
SELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where provider = 'hubspot';
SELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133
SELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null
# [PASSWORD_DOTS]
select * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';
select
cp.*
# DISTINCT t.id
# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields
FROM crm_profiles cp
JOIN crm_configurations crm on crm.id = cp.crm_configuration_id
JOIN users u on u.id = cp.user_id
JOIN teams t ON t.id = crm.team_id
WHERE crm.provider = 'salesforce' and t.status = 'active'
and cp.archived_at IS NULL and u.deleted_at IS NULL
and t.id NOT IN (1093)
and t.id = 2
and cp.contact_fields IS NULL;
# and c.crm_provider_id = '003Uu00000ojD4NIAU';
SELECT * FROM users WHERE id = 26484;
SELECT * FROM crm_profiles WHERE user_id = 26484;
SELECT * FROM social_accounts WHERE sociable_id = 26484;
SELECT * FROM crm_configurations where provider = 'salesforce';
select * from users where id IN (10022, 10403);
select * from users where team_id IN (526);
select * from teams where id IN (526, 532);
select * from crm_configurations where id IN (500, 516);
select * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);
select * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';
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 = 526 and sa.provider = 'salesforce';
select * from team_settings where team_id IN (526, 532);
select * from users where id IN (22824);
select * from crm_profiles where crm_configuration_id IN (1026);
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 = 1093 and sa.provider = 'salesforce';
select * from teams where id = 1099;
select * from users where id = 29643
select * from activity_processing_states;
SELECT * FROM teams where name LIKE '%Fare%'; # 233
SELECT * FROM opportunities where crm_configuration_id = 215
# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'
;
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 = 1088 and sa.provider = 'hubspot';
SELECT * FROM teams order by updated_at DESC
SELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account
select * from crm_configurations where provider = 'pipedrive';
select * from teams where id = 957;
select * from crm_configurations where id = 957;
SELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743
SELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;
select * from users where team_id = 1; # 26726 - Gabriela Dureva
SELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific
select * from activities where user_id = 26726 order by id desc;
select * from contacts where crm_configuration_id = 1
and email IN ('[EMAIL]', '[EMAIL]'); # 2094416, 2093620
SELECT * FROM contacts WHERE id = 6284931;
SELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id
WHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;
select * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);
select * from crm_configurations where id = 1;
43801692-1aeb-32ce-acba-5b80a479701a
44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b
405975c0-b3d0-7aaa-821f-09d59cae6dd1
4caf848d-4bed-2299-b248-7788d41f9fca
49bedc3f-f196-eef3-89c3-dea6a3b4aa63
43420989-a09d-b8f8-9806-c8bbf7a02aac
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 = 1 and sa.provider = 'salesforce';
SELECT * FROM activities WHERE id = 75461988;
SELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;
select * from contacts where id = 17900517;
select * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id
where crm.provider != 'salesforce';
select * from users where id = 21047;
SELECT * FROM crm_configurations WHERE id = 892;
SELECT * FROM teams WHERE id = 942;
select * from opportunities where team_id = 942 order by updated_at desc;
select * from contacts where team_id = 942 order by updated_at desc;
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 = 942 and sa.provider = 'hubspot';
SELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430
SELECT * FROM crm_configurations WHERE id = 1;
SELECT * FROM teams WHERE crm_id = 1;
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 = 1 and sa.provider = 'salesforce';
select id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1
SELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430
select * from teams where id = 852;
select * from groups where id = 2286;
select * from sidekick_settings where team_id = 852;
select * from default_activity_types where team_id = 852;
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1 AND u.deleted_at IS NULL
AND u.crm_required = 1
AND u.team_id = 1
ORDER BY u.team_id;
SELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (
18481
);
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1
AND u.deleted_at IS NULL
AND u.crm_required = 1
# AND u.team_id = 1
AND p.id IS NULL -- Move this condition to WHERE clause
ORDER BY u.team_id;
SELECT * FROM opportunities WHERE id = 20002609;
select * from teams where id = 1122; # Velatir, 29953 - [EMAIL]
select * from crm_configurations where id = 1060;
select * from crm_layouts where crm_configuration_id = 1060;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;
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 = 1122 and sa.provider = 'hubspot';
select * from opportunities where team_id = 1122 order by updated_at desc;
select * from crm_field_data where object_type = 'contact';
SELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262
SELECT
CONCAT(u.id, CASE WHEN u.id = t.own...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85326
|
2921
|
43
|
2026-05-28T12:18:14.771303+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970694771_m2.jpg...
|
PhpStorm
|
faVsco.js – HubspotJournalPollingService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
Execute
Explain Plan
Browse Query History
View Parameters...
|
[{"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":"#12121 on JY-20963-fix-import-on-deleted-entity, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.11569149,"height":0.025538707},"on_screen":true,"help_text":"Pull request #12121 exists for current branch JY-20963-fix-import-on-deleted-entity","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.8374335,"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":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"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 'TextRelayServiceTest'","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 'TextRelayServiceTest'","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":"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":"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":"AXStaticText","text":"12","depth":4,"bounds":{"left":0.37865692,"top":0.15003991,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"9","depth":4,"bounds":{"left":0.39029256,"top":0.15003991,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39993352,"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.40724733,"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\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.41589096,"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.4245346,"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.43550533,"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.44414893,"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}]...
|
2304865042875794005
|
-30141114868883220
|
typing_pause
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
Execute
Explain Plan
Browse Query History
View Parameters...
|
85325
|
NULL
|
NULL
|
NULL
|
|
85325
|
2921
|
42
|
2026-05-28T12:18:13.530299+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970693530_m2.jpg...
|
PhpStorm
|
faVsco.js – HubspotJournalPollingService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny...
|
[{"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":"#12121 on JY-20963-fix-import-on-deleted-entity, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.11569149,"height":0.025538707},"on_screen":true,"help_text":"Pull request #12121 exists for current branch JY-20963-fix-import-on-deleted-entity","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.8374335,"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":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"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 'TextRelayServiceTest'","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 'TextRelayServiceTest'","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":"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":"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":"AXStaticText","text":"12","depth":4,"bounds":{"left":0.37865692,"top":0.15003991,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"9","depth":4,"bounds":{"left":0.39029256,"top":0.15003991,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39993352,"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.40724733,"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\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.41589096,"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.4245346,"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.43550533,"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.44414893,"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.45279256,"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.4637633,"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.47473404,"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.5013298,"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.51230055,"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.7237367,"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}]...
|
-6565421303692608700
|
-30141114868883220
|
typing_pause
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85324
|
2920
|
41
|
2026-05-28T12:18:13.007798+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970693007_m1.jpg...
|
PhpStorm
|
faVsco.js – HubspotJournalPollingService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
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
45
1
41
66
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993
SELECT * FROM users WHERE id = 25061;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 994;
SELECT * FROM crm_profiles WHERE user_id = 25061;
select * from crm_configurations where id = 834;
SELECT * FROM teams WHERE id = 882;
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 = 882 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;
SELECT * FROM contacts where crm_configuration_id = 834;
SELECT * FROM opportunities WHERE team_id = 933
# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');
AND id IN (8482561,18352941,19042734,19232139,19445140,19472541);
SELECT * FROM opportunity_contacts
WHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 485; #
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
select crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id
where crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')
# and l.converted_at IS NOT NULL
;
# [PASSWORD_DOTS]
SELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')
and opportunity_id IS NULL
order by id desc;
SELECT * FROM teams WHERE id = 604; # 598
SELECT * FROM activities WHERE id = 74410828; # [EMAIL]
SELECT * FROM accounts WHERE id = 20068382;
SELECT * FROM accounts WHERE id = 35186038;
SELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;
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 = 559 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;
select * from sidekick_settings where team_id = 781;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100
SELECT * FROM crm_layouts WHERE crm_configuration_id = 711;
SELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL
and is_internal = 0 and status = 'completed'
order by id desc;
SELECT * FROM crm_layout_entities
WHERE crm_layout_id IN (2352, 2353);
;
SELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;
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 = 556 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;
SELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;
select * from contacts
where crm_configuration_id = 530
and crm_provider_id = 872252;
select * from activities where crm_configuration_id = 530
and user_id = 14343 and type like '%softphone%'
and created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);
SELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t
JOIN crm_configurations c ON t.id = c.team_id
WHERE t.status = 'active';
SELECT * FROM teams where id = 1091;
SELECT * FROM crm_configurations where team_id = 1091;
SELECT * FROM activity_providers where team_id = 1091;
SELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT * FROM teams WHERE name LIKE '%Leadventure%';
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 = 1091 and sa.provider = 'salesforce';
SELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812
SELECT * FROM teams where id = 862;
SELECT * FROM crm_configurations where team_id = 862;
SELECT * FROM activity_providers where team_id = 862;
SELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT t.id, crm.id, crm.provider, ap.* FROM teams t
join crm_configurations crm on t.id = crm.team_id
join activity_providers ap on t.id = ap.team_id
where t.status = 'active' and ap.is_enabled = 1
and crm.provider = 'hubspot'
and ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',
'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');
SELECT * FROM teams where id = 1068;
SELECT * FROM crm_configurations where team_id = 1068;
SELECT * FROM activity_providers where team_id = 1068;
SELECT * FROM activities a
where crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')
and a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'
)
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by a.id desc;
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 = 1068 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262
SELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;
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 = 882 and sa.provider = 'hubspot';
select * from crm_layouts where crm_configuration_id = 834;
select * from crm_layout_entities where crm_layout_id = 2780;
select * from crm_fields where id IN (321153,321192,321193,321194);
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
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 = 1057 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8
SELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20
SELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10
SELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #
SELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;
select * from users where team_id = 51; # 7783
SELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130
select * from activity_searches where user_id = 7783;
select * from activity_search_filters where activity_search_id IN (32291, 32292);
SELECT asf.activity_search_id, asf.id, asf.value
FROM activity_search_filters asf
WHERE asf.filter = 'group_id'
AND asf.value IN (
SELECT CONCAT(
HEX(SUBSTR(uuid, 5, 4)), '-',
HEX(SUBSTR(uuid, 3, 2)), '-',
HEX(SUBSTR(uuid, 1, 2)), '-',
HEX(SUBSTR(uuid, 9, 2)), '-',
HEX(SUBSTR(uuid, 11))
)
FROM groups
WHERE deleted_at IS NOT NULL
);
SELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where provider = 'hubspot';
SELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133
SELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null
# [PASSWORD_DOTS]
select * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';
select
cp.*
# DISTINCT t.id
# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields
FROM crm_profiles cp
JOIN crm_configurations crm on crm.id = cp.crm_configuration_id
JOIN users u on u.id = cp.user_id
JOIN teams t ON t.id = crm.team_id
WHERE crm.provider = 'salesforce' and t.status = 'active'
and cp.archived_at IS NULL and u.deleted_at IS NULL
and t.id NOT IN (1093)
and t.id = 2
and cp.contact_fields IS NULL;
# and c.crm_provider_id = '003Uu00000ojD4NIAU';
SELECT * FROM users WHERE id = 26484;
SELECT * FROM crm_profiles WHERE user_id = 26484;
SELECT * FROM social_accounts WHERE sociable_id = 26484;
SELECT * FROM crm_configurations where provider = 'salesforce';
select * from users where id IN (10022, 10403);
select * from users where team_id IN (526);
select * from teams where id IN (526, 532);
select * from crm_configurations where id IN (500, 516);
select * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);
select * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';
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 = 526 and sa.provider = 'salesforce';
select * from team_settings where team_id IN (526, 532);
select * from users where id IN (22824);
select * from crm_profiles where crm_configuration_id IN (1026);
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 = 1093 and sa.provider = 'salesforce';
select * from teams where id = 1099;
select * from users where id = 29643
select * from activity_processing_states;
SELECT * FROM teams where name LIKE '%Fare%'; # 233
SELECT * FROM opportunities where crm_configuration_id = 215
# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'
;
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 = 1088 and sa.provider = 'hubspot';
SELECT * FROM teams order by updated_at DESC
SELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account
select * from crm_configurations where provider = 'pipedrive';
select * from teams where id = 957;
select * from crm_configurations where id = 957;
SELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743
SELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;
select * from users where team_id = 1; # 26726 - Gabriela Dureva
SELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific
select * from activities where user_id = 26726 order by id desc;
select * from contacts where crm_configuration_id = 1
and email IN ('[EMAIL]', '[EMAIL]'); # 2094416, 2093620
SELECT * FROM contacts WHERE id = 6284931;
SELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id
WHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;
select * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);
select * from crm_configurations where id = 1;
43801692-1aeb-32ce-acba-5b80a479701a
44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b
405975c0-b3d0-7aaa-821f-09d59cae6dd1
4caf848d-4bed-2299-b248-7788d41f9fca
49bedc3f-f196-eef3-89c3-dea6a3b4aa63
43420989-a09d-b8f8-9806-c8bbf7a02aac
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 = 1 and sa.provider = 'salesforce';
SELECT * FROM activities WHERE id = 75461988;
SELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;
select * from contacts where id = 17900517;
select * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id
where crm.provider != 'salesforce';
select * from users where id = 21047;
SELECT * FROM crm_configurations WHERE id = 892;
SELECT * FROM teams WHERE id = 942;
select * from opportunities where team_id = 942 order by updated_at desc;
select * from contacts where team_id = 942 order by updated_at desc;
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 = 942 and sa.provider = 'hubspot';
SELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430
SELECT * FROM crm_configurations WHERE id = 1;
SELECT * FROM teams WHERE crm_id = 1;
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 = 1 and sa.provider = 'salesforce';
select id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1
SELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430
select * from teams where id = 852;
select * from groups where id = 2286;
select * from sidekick_settings where team_id = 852;
select * from default_activity_types where team_id = 852;
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1 AND u.deleted_at IS NULL
AND u.crm_required = 1
AND u.team_id = 1
ORDER BY u.team_id;
SELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (
18481
);
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1
AND u.deleted_at IS NULL
AND u.crm_required = 1
# AND u.team_id = 1
AND p.id IS NULL -- Move this condition to WHERE clause
ORDER BY u.team_id;
SELECT * FROM opportunities WHERE id = 20002609;
select * from teams where id = 1122; # Velatir, 29953 - [EMAIL]
select * from crm_configurations where id = 1060;
select * from crm_layouts where crm_configuration_id = 1060;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;
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 = 1122 and sa.provider = 'hubspot';
select * from opportunities where team_id = 1122 order by updated_at desc;
select * from crm_field_data where object_type = 'contact';
SELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262
SELECT
CONCAT(u.id, CASE WHEN u.id = t.own...
|
[{"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":"#12121 on JY-20963-fix-import-on-deleted-entity, menu","depth":5,"on_screen":true,"help_text":"Pull request #12121 exists for current branch JY-20963-fix-import-on-deleted-entity","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":"TextRelayServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","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":"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":"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":"AXStaticText","text":"12","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"9","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\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","role_description":"text entry area","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":"45","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"41","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"66","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":"SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993\nSELECT * FROM users WHERE id = 25061;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 994;\nSELECT * FROM crm_profiles WHERE user_id = 25061;\n\nselect * from crm_configurations where id = 834;\nSELECT * FROM teams WHERE id = 882;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;\n\nSELECT * FROM contacts where crm_configuration_id = 834;\nSELECT * FROM opportunities WHERE team_id = 933\n# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');\nAND id IN (8482561,18352941,19042734,19232139,19445140,19472541);\nSELECT * FROM opportunity_contacts\nWHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 485; #\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nselect crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id\nwhere crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')\n# and l.converted_at IS NOT NULL\n;\n\n# ********************************************************************\nSELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')\nand opportunity_id IS NULL\norder by id desc;\n\nSELECT * FROM teams WHERE id = 604; # 598\nSELECT * FROM activities WHERE id = 74410828; # chelseaw@allvoices.co\nSELECT * FROM accounts WHERE id = 20068382;\nSELECT * FROM accounts WHERE id = 35186038;\n\nSELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 559 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;\nselect * from sidekick_settings where team_id = 781;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100\n\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 711;\nSELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL\nand is_internal = 0 and status = 'completed'\norder by id desc;\n\nSELECT * FROM crm_layout_entities\nWHERE crm_layout_id IN (2352, 2353);\n;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 556 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;\nSELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;\nselect * from contacts\nwhere crm_configuration_id = 530\nand crm_provider_id = 872252;\n\nselect * from activities where crm_configuration_id = 530\nand user_id = 14343 and type like '%softphone%'\nand created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);\n\n\nSELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t\nJOIN crm_configurations c ON t.id = c.team_id\nWHERE t.status = 'active';\n\nSELECT * FROM teams where id = 1091;\nSELECT * FROM crm_configurations where team_id = 1091;\nSELECT * FROM activity_providers where team_id = 1091;\nSELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT * FROM teams WHERE name LIKE '%Leadventure%';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1091 and sa.provider = 'salesforce';\n\nSELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812\nSELECT * FROM teams where id = 862;\nSELECT * FROM crm_configurations where team_id = 862;\nSELECT * FROM activity_providers where team_id = 862;\nSELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT t.id, crm.id, crm.provider, ap.* FROM teams t\njoin crm_configurations crm on t.id = crm.team_id\njoin activity_providers ap on t.id = ap.team_id\nwhere t.status = 'active' and ap.is_enabled = 1\nand crm.provider = 'hubspot'\nand ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',\n 'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');\n\nSELECT * FROM teams where id = 1068;\nSELECT * FROM crm_configurations where team_id = 1068;\nSELECT * FROM activity_providers where team_id = 1068;\n\nSELECT * FROM activities a\nwhere crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')\nand a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'\n )\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by a.id desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1068 and sa.provider = 'hubspot';\n\n# ********************************************************************\n# ********************************************************************\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262\nSELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\nselect * from crm_layouts where crm_configuration_id = 834;\nselect * from crm_layout_entities where crm_layout_id = 2780;\nselect * from crm_fields where id IN (321153,321192,321193,321194);\n\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1057 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8\n\nSELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20\n\nSELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10\n\nSELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #\n\nSELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;\nselect * from users where team_id = 51; # 7783\nSELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130\nselect * from activity_searches where user_id = 7783;\nselect * from activity_search_filters where activity_search_id IN (32291, 32292);\n\nSELECT asf.activity_search_id, asf.id, asf.value\nFROM activity_search_filters asf\nWHERE asf.filter = 'group_id'\nAND asf.value IN (\n SELECT CONCAT(\n HEX(SUBSTR(uuid, 5, 4)), '-',\n HEX(SUBSTR(uuid, 3, 2)), '-',\n HEX(SUBSTR(uuid, 1, 2)), '-',\n HEX(SUBSTR(uuid, 9, 2)), '-',\n HEX(SUBSTR(uuid, 11))\n )\n FROM groups\n WHERE deleted_at IS NOT NULL\n);\n\nSELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th\n# ********************************************************************\nSELECT * FROM crm_configurations where provider = 'hubspot';\nSELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133\nSELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null\n# ********************************************************************\n\nselect * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';\nselect\n cp.*\n# DISTINCT t.id\n# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields\nFROM crm_profiles cp\nJOIN crm_configurations crm on crm.id = cp.crm_configuration_id\nJOIN users u on u.id = cp.user_id\nJOIN teams t ON t.id = crm.team_id\nWHERE crm.provider = 'salesforce' and t.status = 'active'\n and cp.archived_at IS NULL and u.deleted_at IS NULL\n and t.id NOT IN (1093)\n and t.id = 2\n and cp.contact_fields IS NULL;\n# and c.crm_provider_id = '003Uu00000ojD4NIAU';\n\nSELECT * FROM users WHERE id = 26484;\nSELECT * FROM crm_profiles WHERE user_id = 26484;\nSELECT * FROM social_accounts WHERE sociable_id = 26484;\nSELECT * FROM crm_configurations where provider = 'salesforce';\nselect * from users where id IN (10022, 10403);\nselect * from users where team_id IN (526);\nselect * from teams where id IN (526, 532);\nselect * from crm_configurations where id IN (500, 516);\nselect * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);\nselect * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 526 and sa.provider = 'salesforce';\nselect * from team_settings where team_id IN (526, 532);\n\nselect * from users where id IN (22824);\nselect * from crm_profiles where crm_configuration_id IN (1026);\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1093 and sa.provider = 'salesforce';\n\nselect * from teams where id = 1099;\nselect * from users where id = 29643\n\nselect * from activity_processing_states;\n\nSELECT * FROM teams where name LIKE '%Fare%'; # 233\nSELECT * FROM opportunities where crm_configuration_id = 215\n# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1088 and sa.provider = 'hubspot';\n\nSELECT * FROM teams order by updated_at DESC\nSELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account\n\nselect * from crm_configurations where provider = 'pipedrive';\n\nselect * from teams where id = 957;\nselect * from crm_configurations where id = 957;\n\nSELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743\nSELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;\n\nselect * from users where team_id = 1; # 26726 - Gabriela Dureva\nSELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific\nselect * from activities where user_id = 26726 order by id desc;\nselect * from contacts where crm_configuration_id = 1\nand email IN ('charlotte.ward@prolific.com', 'frankie.bryant@prolific.com'); # 2094416, 2093620\nSELECT * FROM contacts WHERE id = 6284931;\n\nSELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id\nWHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;\n\nselect * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);\nselect * from crm_configurations where id = 1;\n\n43801692-1aeb-32ce-acba-5b80a479701a\n44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b\n405975c0-b3d0-7aaa-821f-09d59cae6dd1\n4caf848d-4bed-2299-b248-7788d41f9fca\n49bedc3f-f196-eef3-89c3-dea6a3b4aa63\n43420989-a09d-b8f8-9806-c8bbf7a02aac\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nSELECT * FROM activities WHERE id = 75461988;\n\nSELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;\n\nselect * from contacts where id = 17900517;\n\nselect * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id\nwhere crm.provider != 'salesforce';\n\nselect * from users where id = 21047;\nSELECT * FROM crm_configurations WHERE id = 892;\nSELECT * FROM teams WHERE id = 942;\nselect * from opportunities where team_id = 942 order by updated_at desc;\nselect * from contacts where team_id = 942 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 942 and sa.provider = 'hubspot';\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430\nSELECT * FROM crm_configurations WHERE id = 1;\nSELECT * FROM teams WHERE crm_id = 1;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nselect id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1\nSELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430\n\nselect * from teams where id = 852;\nselect * from groups where id = 2286;\nselect * from sidekick_settings where team_id = 852;\nselect * from default_activity_types where team_id = 852;\n\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1 AND u.deleted_at IS NULL\nAND u.crm_required = 1\nAND u.team_id = 1\nORDER BY u.team_id;\n\nSELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (\n18481\n );\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1\n AND u.deleted_at IS NULL\n AND u.crm_required = 1\n# AND u.team_id = 1\n AND p.id IS NULL -- Move this condition to WHERE clause\nORDER BY u.team_id;\n\nSELECT * FROM opportunities WHERE id = 20002609;\nselect * from teams where id = 1122; # Velatir, 29953 - christian@velatir.com\nselect * from crm_configurations where id = 1060;\nselect * from crm_layouts where crm_configuration_id = 1060;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1122 and sa.provider = 'hubspot';\nselect * from opportunities where team_id = 1122 order by updated_at desc;\n\nselect * from crm_field_data where object_type = 'contact';\n\nSELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 248 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS\nSELECT * FROM users where id = 24115;\nSELECT * FROM accounts where id = 4002896;\nSELECT * FROM teams WHERE name LIKE '%adswerve%';\nSELECT * FROM opportunities where crm_configuration_id = 230 AND crm_provider_id IN (\"0069N000003GIQ9QAO\",\"0061r000019yGP9AAM\",\"0066900001S2KWlAAN\",\"0066900001TDpj2AAD\",\"0066900001b8uEwAAI\",\"0069N000001rQi0QAE\",\"006QF00000KD40mYAD\",\"006QF00000LzpRJYAZ\",\"0069N000002uomtQAA\",\"0069N000002xlMLQAY\",\"0066900001NV6ubAAD\",\"0061r00001HJp45AAD\",\"006QF00000uTlUoYAK\",\"006QF00000v0bZqYAI\");\nSELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203\n\nSELECT u.id, u.email, ac.name, a.* FROM activities a\nJOIN users u ON a.user_id = u.id\nJOIN accounts ac ON a.account_id = ac.id\nWHERE\nuuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or\nuuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or\nuuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;\n\nselect * from users where id = 5825;\nSELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;\n\nselect * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;\n19594, 862\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 862 and sa.provider = 'salesforce';\n\nselect * from automated_reports where id = 36;\nselect ar.frequency, r.*, ar.* from automated_report_results r\njoin automated_reports ar on r.report_id = ar.id\nwhere ar.frequency != 'one_off';\n\nselect s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;\nselect * from nudges n where n.activity_search_id\n\nselect * from teams where created_at > '2026-03-09';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;\n\nselect * from users where team_id = 1 and name like '%Lukas%'; # 7160\n\nSELECT * FROM teams WHERE id = 575;\nselect * from opportunities where team_id = 575;\nSELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,\nselect * from opportunities where team_id = 1126;\nSELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,\nselect * from opportunities where team_id = 1125;\nselect * from contacts c\nwhere c.team_id = 882;\n\nSELECT * FROM activities WHERE id = 76822967;\nSELECT * FROM crm_profiles WHERE user_id = 15440;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 555;\nSELECT * FROM crm_configurations WHERE id = 555;\nSELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 581 and sa.provider = 'salesforce';\n\nSELECT * FROM automated_report_results order by id desc;\n\nselect * from features;\nselect * from team_features where feature_id = 40;\n\nselect * from teams where id = 556;\n\nselect * from automated_reports;\nwhere id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , [\"pdf\",\"podcast\"]\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\nselect * from automated_report_results order by id desc;\nSELECT * FROM automated_report_results WHERE id = 1919;\n\nselect * from automated_report_results WHERE report_id = 54;\n\nselect * from opportunities where id = 7594349;\n\nSELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067 - jiminnyintegration@lesmills.com\nselect * from playbooks where team_id = 711; # event 226147\nSELECT * FROM playbook_categories WHERE playbook_id = 5515;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event';\nSELECT * FROM crm_fields WHERE id = 226147;\nSELECT * FROM crm_field_values WHERE crm_field_id = 226147;\n\nSELECT * FROM crm_configurations WHERE id = 692;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 711 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles cp JOIN users u on u.id = cp.user_id WHERE u.team_id = 711;\n\nselect * from leads;\n\nselect * from calendars;\n\nSELECT\n t.id AS team_id,\n t.name,\n LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1)) AS calendar_domain\nFROM teams t\nJOIN users u ON u.team_id = t.id\nJOIN calendars c ON c.user_id = u.id AND c.status = 'active' AND c.calendar_provider_id LIKE '%@%'\nLEFT JOIN team_domains td\n ON td.team_id = t.id\n AND td.deleted_at IS NULL\n AND td.domain = LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1))\nGROUP BY t.id, t.name, calendar_domain\nORDER BY t.name, calendar_domain;\n\nselect * from users u join calendars c on c.user_id = u.id\nwhere u.team_id = 882;\n\n\nselect * from activities where id = 74049485; # team 563 crm 537\nselect * from activities where id = 73272382; # team 563 crm 537\nselect * from activities where id = 64400389; # team 563 crm 537\nselect * from activities where id = 58081273; # team 563 crm 537\nselect * from activities where id = 54520297; # team 563 crm 537\nselect * from participants where activity_id = 58081273;\n\nselect * from activities where crm_configuration_id = 537 and provider = 'aircall'\nand account_id = 19003658 order by updated_at desc;\n\nselect * from contacts where crm_configuration_id = 537 and id = 35957759;\nselect * from accounts where crm_configuration_id = 537 and id = 19003658;\n\nselect * from automated_report_results where id = 1976;\nselect * from automated_reports where id = 583;\nselect * from activity_searches where id = 87714;\nselect * from activity_search_filters where activity_search_id = 87714;\n\nSELECT * FROM activities WHERE uuid_to_bin('8827f672-202d-4162-9d04-73ff5f0566a9') = uuid\nor uuid_to_bin('47842446-af51-4bcb-854f-cc6560290101') = uuid;\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot';\nselect * from rate_limits;\n\nselect * from automated_report_results where media_type = 'pdf' and status = 2\nand id IN (18, 1872);\nselect * from automated_reports where id = 54;\nSELECT * FROM users WHERE id IN (24623,29443,29613);\n\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\n\nselect * from text_relays where created_at > '2026-05-01';\nand id IN (32415, 32416);\n# and id = 32412;\n\nselect * from users where team_id = 2 and email like '%scott%' and id = 29510;\n\nSELECT * FROM activities WHERE uuid_to_bin('67cebfc2-ed56-44a2-8c68-7a0286ed8618') = uuid; # 79763436\n\nSELECT email_provider_id, COUNT(*) as count, GROUP_CONCAT(id) as ids, GROUP_CONCAT(status) as statuses\nFROM text_relays\nWHERE email_provider_id IN ('19e2027868a64b42', '19e2033ed8ea6b10')\nGROUP BY email_provider_id;\nSELECT id, status, telephony_provider_id, created_at\nFROM activities\nWHERE id IN (80028719, 80028846);\nSELECT id, status, code, email_sent_at, created_at, updated_at\nFROM text_relays\nWHERE id IN (32415, 32416);\nSELECT id, status, code, sender, recipient, created_at\nFROM text_relays\nWHERE sender LIKE '%mario.georgiev%' OR sender LIKE '%stoyan.tomov%'\nORDER BY created_at DESC\nLIMIT 10;\n\nSELECT id, uuid, status, code, sender, recipient, created_at, updated_at\nFROM text_relays\nWHERE uuid = uuid_to_bin('0626141c-27a6-4d8c-aff8-7c8020a2c656');\n\n# ***************\nSELECT DISTINCT u.id, u.email, u.name, u.softphone_number, COUNT(a.id) as sms_count\nFROM users u\nINNER JOIN activities a ON u.id = .user_id\nWHERE a.type LIKE 'sms%'\nAND a.created_at > DATE_SUB(NOW(), INTERVAL 30 DAY)\nGROUP BY u.id, u.email, u.name, u.softphone_number\nORDER BY sms_count DESC;\n\nselect * from teams where id = 1;\n\nselect * from roles;\n\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1117 and sa.provider = 'hubspot';\nSELECT * FROM activities WHERE uuid_to_bin('8024fffb-2df7-4017-91f4-d9f896050248') = uuid; # 79933459 YES\nSELECT * FROM activities WHERE uuid_to_bin('25529043-8094-4781-927f-4f4da2a8185c') = uuid; # 80186192 NO\nSELECT * FROM crm_configurations WHERE id = 1053;\nSELECT * FROM teams WHERE id = 1117;\nselect * from users where id = 30249;\nselect * from playbooks where id = 5473;\nselect * from playbook_categories where id = 43783;\nselect * from playbook_categories where playbook_id = 5473;\nselect * from crm_fields where id = 659242;\nselect * from crm_field_values where crm_field_id = 659242;\n\nSELECT * FROM crm_field_data fd\n# JOIN crm_fields f ON fd.crm_field_id = f.id\n# JOIN activities a ON fd.activity_id = a.id\nWHERE activity_id = 79933459\n# AND f.crm_provider_id = 'hs_activity_type';\n\n\nSELECT * FROM activity_messages;\nselect * from text_relays where created_at > '2026-05-01';\nselect * from activities where user_id IN (7160, 18608) and created_at > '2026-05-22' order by id desc;\nselect * from users where team_id = 1 and id IN (18608, 13934, 7160);\nselect * from activities where user_id = 7160 order by id desc limit 10;\n\nselect * from accounts where team_id = 1 and name = 'Column5';\n\nselect * from users where name like '%Subra%'; # 31054, 1117\nselect * from teams where id = 1117;\nselect * from activity_searches where user_id = 31054;\nselect * from activity_search_filters where activity_search_id IN (88882, 88902);","depth":4,"on_screen":true,"value":"SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993\nSELECT * FROM users WHERE id = 25061;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 994;\nSELECT * FROM crm_profiles WHERE user_id = 25061;\n\nselect * from crm_configurations where id = 834;\nSELECT * FROM teams WHERE id = 882;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;\n\nSELECT * FROM contacts where crm_configuration_id = 834;\nSELECT * FROM opportunities WHERE team_id = 933\n# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');\nAND id IN (8482561,18352941,19042734,19232139,19445140,19472541);\nSELECT * FROM opportunity_contacts\nWHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 485; #\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nselect crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id\nwhere crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')\n# and l.converted_at IS NOT NULL\n;\n\n# ********************************************************************\nSELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')\nand opportunity_id IS NULL\norder by id desc;\n\nSELECT * FROM teams WHERE id = 604; # 598\nSELECT * FROM activities WHERE id = 74410828; # chelseaw@allvoices.co\nSELECT * FROM accounts WHERE id = 20068382;\nSELECT * FROM accounts WHERE id = 35186038;\n\nSELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 559 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;\nselect * from sidekick_settings where team_id = 781;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100\n\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 711;\nSELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL\nand is_internal = 0 and status = 'completed'\norder by id desc;\n\nSELECT * FROM crm_layout_entities\nWHERE crm_layout_id IN (2352, 2353);\n;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 556 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;\nSELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;\nselect * from contacts\nwhere crm_configuration_id = 530\nand crm_provider_id = 872252;\n\nselect * from activities where crm_configuration_id = 530\nand user_id = 14343 and type like '%softphone%'\nand created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);\n\n\nSELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t\nJOIN crm_configurations c ON t.id = c.team_id\nWHERE t.status = 'active';\n\nSELECT * FROM teams where id = 1091;\nSELECT * FROM crm_configurations where team_id = 1091;\nSELECT * FROM activity_providers where team_id = 1091;\nSELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT * FROM teams WHERE name LIKE '%Leadventure%';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1091 and sa.provider = 'salesforce';\n\nSELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812\nSELECT * FROM teams where id = 862;\nSELECT * FROM crm_configurations where team_id = 862;\nSELECT * FROM activity_providers where team_id = 862;\nSELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT t.id, crm.id, crm.provider, ap.* FROM teams t\njoin crm_configurations crm on t.id = crm.team_id\njoin activity_providers ap on t.id = ap.team_id\nwhere t.status = 'active' and ap.is_enabled = 1\nand crm.provider = 'hubspot'\nand ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',\n 'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');\n\nSELECT * FROM teams where id = 1068;\nSELECT * FROM crm_configurations where team_id = 1068;\nSELECT * FROM activity_providers where team_id = 1068;\n\nSELECT * FROM activities a\nwhere crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')\nand a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'\n )\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by a.id desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1068 and sa.provider = 'hubspot';\n\n# ********************************************************************\n# ********************************************************************\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262\nSELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\nselect * from crm_layouts where crm_configuration_id = 834;\nselect * from crm_layout_entities where crm_layout_id = 2780;\nselect * from crm_fields where id IN (321153,321192,321193,321194);\n\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1057 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8\n\nSELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20\n\nSELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10\n\nSELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #\n\nSELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;\nselect * from users where team_id = 51; # 7783\nSELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130\nselect * from activity_searches where user_id = 7783;\nselect * from activity_search_filters where activity_search_id IN (32291, 32292);\n\nSELECT asf.activity_search_id, asf.id, asf.value\nFROM activity_search_filters asf\nWHERE asf.filter = 'group_id'\nAND asf.value IN (\n SELECT CONCAT(\n HEX(SUBSTR(uuid, 5, 4)), '-',\n HEX(SUBSTR(uuid, 3, 2)), '-',\n HEX(SUBSTR(uuid, 1, 2)), '-',\n HEX(SUBSTR(uuid, 9, 2)), '-',\n HEX(SUBSTR(uuid, 11))\n )\n FROM groups\n WHERE deleted_at IS NOT NULL\n);\n\nSELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th\n# ********************************************************************\nSELECT * FROM crm_configurations where provider = 'hubspot';\nSELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133\nSELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null\n# ********************************************************************\n\nselect * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';\nselect\n cp.*\n# DISTINCT t.id\n# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields\nFROM crm_profiles cp\nJOIN crm_configurations crm on crm.id = cp.crm_configuration_id\nJOIN users u on u.id = cp.user_id\nJOIN teams t ON t.id = crm.team_id\nWHERE crm.provider = 'salesforce' and t.status = 'active'\n and cp.archived_at IS NULL and u.deleted_at IS NULL\n and t.id NOT IN (1093)\n and t.id = 2\n and cp.contact_fields IS NULL;\n# and c.crm_provider_id = '003Uu00000ojD4NIAU';\n\nSELECT * FROM users WHERE id = 26484;\nSELECT * FROM crm_profiles WHERE user_id = 26484;\nSELECT * FROM social_accounts WHERE sociable_id = 26484;\nSELECT * FROM crm_configurations where provider = 'salesforce';\nselect * from users where id IN (10022, 10403);\nselect * from users where team_id IN (526);\nselect * from teams where id IN (526, 532);\nselect * from crm_configurations where id IN (500, 516);\nselect * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);\nselect * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 526 and sa.provider = 'salesforce';\nselect * from team_settings where team_id IN (526, 532);\n\nselect * from users where id IN (22824);\nselect * from crm_profiles where crm_configuration_id IN (1026);\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1093 and sa.provider = 'salesforce';\n\nselect * from teams where id = 1099;\nselect * from users where id = 29643\n\nselect * from activity_processing_states;\n\nSELECT * FROM teams where name LIKE '%Fare%'; # 233\nSELECT * FROM opportunities where crm_configuration_id = 215\n# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1088 and sa.provider = 'hubspot';\n\nSELECT * FROM teams order by updated_at DESC\nSELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account\n\nselect * from crm_configurations where provider = 'pipedrive';\n\nselect * from teams where id = 957;\nselect * from crm_configurations where id = 957;\n\nSELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743\nSELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;\n\nselect * from users where team_id = 1; # 26726 - Gabriela Dureva\nSELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific\nselect * from activities where user_id = 26726 order by id desc;\nselect * from contacts where crm_configuration_id = 1\nand email IN ('charlotte.ward@prolific.com', 'frankie.bryant@prolific.com'); # 2094416, 2093620\nSELECT * FROM contacts WHERE id = 6284931;\n\nSELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id\nWHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;\n\nselect * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);\nselect * from crm_configurations where id = 1;\n\n43801692-1aeb-32ce-acba-5b80a479701a\n44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b\n405975c0-b3d0-7aaa-821f-09d59cae6dd1\n4caf848d-4bed-2299-b248-7788d41f9fca\n49bedc3f-f196-eef3-89c3-dea6a3b4aa63\n43420989-a09d-b8f8-9806-c8bbf7a02aac\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nSELECT * FROM activities WHERE id = 75461988;\n\nSELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;\n\nselect * from contacts where id = 17900517;\n\nselect * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id\nwhere crm.provider != 'salesforce';\n\nselect * from users where id = 21047;\nSELECT * FROM crm_configurations WHERE id = 892;\nSELECT * FROM teams WHERE id = 942;\nselect * from opportunities where team_id = 942 order by updated_at desc;\nselect * from contacts where team_id = 942 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 942 and sa.provider = 'hubspot';\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430\nSELECT * FROM crm_configurations WHERE id = 1;\nSELECT * FROM teams WHERE crm_id = 1;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nselect id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1\nSELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430\n\nselect * from teams where id = 852;\nselect * from groups where id = 2286;\nselect * from sidekick_settings where team_id = 852;\nselect * from default_activity_types where team_id = 852;\n\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1 AND u.deleted_at IS NULL\nAND u.crm_required = 1\nAND u.team_id = 1\nORDER BY u.team_id;\n\nSELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (\n18481\n );\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1\n AND u.deleted_at IS NULL\n AND u.crm_required = 1\n# AND u.team_id = 1\n AND p.id IS NULL -- Move this condition to WHERE clause\nORDER BY u.team_id;\n\nSELECT * FROM opportunities WHERE id = 20002609;\nselect * from teams where id = 1122; # Velatir, 29953 - christian@velatir.com\nselect * from crm_configurations where id = 1060;\nselect * from crm_layouts where crm_configuration_id = 1060;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1122 and sa.provider = 'hubspot';\nselect * from opportunities where team_id = 1122 order by updated_at desc;\n\nselect * from crm_field_data where object_type = 'contact';\n\nSELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 248 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS\nSELECT * FROM users where id = 24115;\nSELECT * FROM accounts where id = 4002896;\nSELECT * FROM teams WHERE name LIKE '%adswerve%';\nSELECT * FROM opportunities where crm_configuration_id = 230 AND crm_provider_id IN (\"0069N000003GIQ9QAO\",\"0061r000019yGP9AAM\",\"0066900001S2KWlAAN\",\"0066900001TDpj2AAD\",\"0066900001b8uEwAAI\",\"0069N000001rQi0QAE\",\"006QF00000KD40mYAD\",\"006QF00000LzpRJYAZ\",\"0069N000002uomtQAA\",\"0069N000002xlMLQAY\",\"0066900001NV6ubAAD\",\"0061r00001HJp45AAD\",\"006QF00000uTlUoYAK\",\"006QF00000v0bZqYAI\");\nSELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203\n\nSELECT u.id, u.email, ac.name, a.* FROM activities a\nJOIN users u ON a.user_id = u.id\nJOIN accounts ac ON a.account_id = ac.id\nWHERE\nuuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or\nuuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or\nuuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;\n\nselect * from users where id = 5825;\nSELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;\n\nselect * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;\n19594, 862\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 862 and sa.provider = 'salesforce';\n\nselect * from automated_reports where id = 36;\nselect ar.frequency, r.*, ar.* from automated_report_results r\njoin automated_reports ar on r.report_id = ar.id\nwhere ar.frequency != 'one_off';\n\nselect s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;\nselect * from nudges n where n.activity_search_id\n\nselect * from teams where created_at > '2026-03-09';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;\n\nselect * from users where team_id = 1 and name like '%Lukas%'; # 7160\n\nSELECT * FROM teams WHERE id = 575;\nselect * from opportunities where team_id = 575;\nSELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,\nselect * from opportunities where team_id = 1126;\nSELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,\nselect * from opportunities where team_id = 1125;\nselect * from contacts c\nwhere c.team_id = 882;\n\nSELECT * FROM activities WHERE id = 76822967;\nSELECT * FROM crm_profiles WHERE user_id = 15440;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 555;\nSELECT * FROM crm_configurations WHERE id = 555;\nSELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 581 and sa.provider = 'salesforce';\n\nSELECT * FROM automated_report_results order by id desc;\n\nselect * from features;\nselect * from team_features where feature_id = 40;\n\nselect * from teams where id = 556;\n\nselect * from automated_reports;\nwhere id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , [\"pdf\",\"podcast\"]\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\nselect * from automated_report_results order by id desc;\nSELECT * FROM automated_report_results WHERE id = 1919;\n\nselect * from automated_report_results WHERE report_id = 54;\n\nselect * from opportunities where id = 7594349;\n\nSELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067 - jiminnyintegration@lesmills.com\nselect * from playbooks where team_id = 711; # event 226147\nSELECT * FROM playbook_categories WHERE playbook_id = 5515;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event';\nSELECT * FROM crm_fields WHERE id = 226147;\nSELECT * FROM crm_field_values WHERE crm_field_id = 226147;\n\nSELECT * FROM crm_configurations WHERE id = 692;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 711 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles cp JOIN users u on u.id = cp.user_id WHERE u.team_id = 711;\n\nselect * from leads;\n\nselect * from calendars;\n\nSELECT\n t.id AS team_id,\n t.name,\n LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1)) AS calendar_domain\nFROM teams t\nJOIN users u ON u.team_id = t.id\nJOIN calendars c ON c.user_id = u.id AND c.status = 'active' AND c.calendar_provider_id LIKE '%@%'\nLEFT JOIN team_domains td\n ON td.team_id = t.id\n AND td.deleted_at IS NULL\n AND td.domain = LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1))\nGROUP BY t.id, t.name, calendar_domain\nORDER BY t.name, calendar_domain;\n\nselect * from users u join calendars c on c.user_id = u.id\nwhere u.team_id = 882;\n\n\nselect * from activities where id = 74049485; # team 563 crm 537\nselect * from activities where id = 73272382; # team 563 crm 537\nselect * from activities where id = 64400389; # team 563 crm 537\nselect * from activities where id = 58081273; # team 563 crm 537\nselect * from activities where id = 54520297; # team 563 crm 537\nselect * from participants where activity_id = 58081273;\n\nselect * from activities where crm_configuration_id = 537 and provider = 'aircall'\nand account_id = 19003658 order by updated_at desc;\n\nselect * from contacts where crm_configuration_id = 537 and id = 35957759;\nselect * from accounts where crm_configuration_id = 537 and id = 19003658;\n\nselect * from automated_report_results where id = 1976;\nselect * from automated_reports where id = 583;\nselect * from activity_searches where id = 87714;\nselect * from activity_search_filters where activity_search_id = 87714;\n\nSELECT * FROM activities WHERE uuid_to_bin('8827f672-202d-4162-9d04-73ff5f0566a9') = uuid\nor uuid_to_bin('47842446-af51-4bcb-854f-cc6560290101') = uuid;\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot';\nselect * from rate_limits;\n\nselect * from automated_report_results where media_type = 'pdf' and status = 2\nand id IN (18, 1872);\nselect * from automated_reports where id = 54;\nSELECT * FROM users WHERE id IN (24623,29443,29613);\n\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\n\nselect * from text_relays where created_at > '2026-05-01';\nand id IN (32415, 32416);\n# and id = 32412;\n\nselect * from users where team_id = 2 and email like '%scott%' and id = 29510;\n\nSELECT * FROM activities WHERE uuid_to_bin('67cebfc2-ed56-44a2-8c68-7a0286ed8618') = uuid; # 79763436\n\nSELECT email_provider_id, COUNT(*) as count, GROUP_CONCAT(id) as ids, GROUP_CONCAT(status) as statuses\nFROM text_relays\nWHERE email_provider_id IN ('19e2027868a64b42', '19e2033ed8ea6b10')\nGROUP BY email_provider_id;\nSELECT id, status, telephony_provider_id, created_at\nFROM activities\nWHERE id IN (80028719, 80028846);\nSELECT id, status, code, email_sent_at, created_at, updated_at\nFROM text_relays\nWHERE id IN (32415, 32416);\nSELECT id, status, code, sender, recipient, created_at\nFROM text_relays\nWHERE sender LIKE '%mario.georgiev%' OR sender LIKE '%stoyan.tomov%'\nORDER BY created_at DESC\nLIMIT 10;\n\nSELECT id, uuid, status, code, sender, recipient, created_at, updated_at\nFROM text_relays\nWHERE uuid = uuid_to_bin('0626141c-27a6-4d8c-aff8-7c8020a2c656');\n\n# ***************\nSELECT DISTINCT u.id, u.email, u.name, u.softphone_number, COUNT(a.id) as sms_count\nFROM users u\nINNER JOIN activities a ON u.id = .user_id\nWHERE a.type LIKE 'sms%'\nAND a.created_at > DATE_SUB(NOW(), INTERVAL 30 DAY)\nGROUP BY u.id, u.email, u.name, u.softphone_number\nORDER BY sms_count DESC;\n\nselect * from teams where id = 1;\n\nselect * from roles;\n\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1117 and sa.provider = 'hubspot';\nSELECT * FROM activities WHERE uuid_to_bin('8024fffb-2df7-4017-91f4-d9f896050248') = uuid; # 79933459 YES\nSELECT * FROM activities WHERE uuid_to_bin('25529043-8094-4781-927f-4f4da2a8185c') = uuid; # 80186192 NO\nSELECT * FROM crm_configurations WHERE id = 1053;\nSELECT * FROM teams WHERE id = 1117;\nselect * from users where id = 30249;\nselect * from playbooks where id = 5473;\nselect * from playbook_categories where id = 43783;\nselect * from playbook_categories where playbook_id = 5473;\nselect * from crm_fields where id = 659242;\nselect * from crm_field_values where crm_field_id = 659242;\n\nSELECT * FROM crm_field_data fd\n# JOIN crm_fields f ON fd.crm_field_id = f.id\n# JOIN activities a ON fd.activity_id = a.id\nWHERE activity_id = 79933459\n# AND f.crm_provider_id = 'hs_activity_type';\n\n\nSELECT * FROM activity_messages;\nselect * from text_relays where created_at > '2026-05-01';\nselect * from activities where user_id IN (7160, 18608) and created_at > '2026-05-22' order by id desc;\nselect * from users where team_id = 1 and id IN (18608, 13934, 7160);\nselect * from activities where user_id = 7160 order by id desc limit 10;\n\nselect * from accounts where team_id = 1 and name = 'Column5';\n\nselect * from users where name like '%Subra%'; # 31054, 1117\nselect * from teams where id = 1117;\nselect * from activity_searches where user_id = 31054;\nselect * from activity_search_filters where activity_search_id IN (88882, 88902);","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":"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}]...
|
2526630547225287665
|
1137771414947247693
|
typing_pause
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
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
45
1
41
66
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993
SELECT * FROM users WHERE id = 25061;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 994;
SELECT * FROM crm_profiles WHERE user_id = 25061;
select * from crm_configurations where id = 834;
SELECT * FROM teams WHERE id = 882;
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 = 882 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;
SELECT * FROM contacts where crm_configuration_id = 834;
SELECT * FROM opportunities WHERE team_id = 933
# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');
AND id IN (8482561,18352941,19042734,19232139,19445140,19472541);
SELECT * FROM opportunity_contacts
WHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 485; #
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
select crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id
where crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')
# and l.converted_at IS NOT NULL
;
# [PASSWORD_DOTS]
SELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')
and opportunity_id IS NULL
order by id desc;
SELECT * FROM teams WHERE id = 604; # 598
SELECT * FROM activities WHERE id = 74410828; # [EMAIL]
SELECT * FROM accounts WHERE id = 20068382;
SELECT * FROM accounts WHERE id = 35186038;
SELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;
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 = 559 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;
select * from sidekick_settings where team_id = 781;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100
SELECT * FROM crm_layouts WHERE crm_configuration_id = 711;
SELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL
and is_internal = 0 and status = 'completed'
order by id desc;
SELECT * FROM crm_layout_entities
WHERE crm_layout_id IN (2352, 2353);
;
SELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;
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 = 556 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;
SELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;
select * from contacts
where crm_configuration_id = 530
and crm_provider_id = 872252;
select * from activities where crm_configuration_id = 530
and user_id = 14343 and type like '%softphone%'
and created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);
SELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t
JOIN crm_configurations c ON t.id = c.team_id
WHERE t.status = 'active';
SELECT * FROM teams where id = 1091;
SELECT * FROM crm_configurations where team_id = 1091;
SELECT * FROM activity_providers where team_id = 1091;
SELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT * FROM teams WHERE name LIKE '%Leadventure%';
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 = 1091 and sa.provider = 'salesforce';
SELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812
SELECT * FROM teams where id = 862;
SELECT * FROM crm_configurations where team_id = 862;
SELECT * FROM activity_providers where team_id = 862;
SELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT t.id, crm.id, crm.provider, ap.* FROM teams t
join crm_configurations crm on t.id = crm.team_id
join activity_providers ap on t.id = ap.team_id
where t.status = 'active' and ap.is_enabled = 1
and crm.provider = 'hubspot'
and ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',
'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');
SELECT * FROM teams where id = 1068;
SELECT * FROM crm_configurations where team_id = 1068;
SELECT * FROM activity_providers where team_id = 1068;
SELECT * FROM activities a
where crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')
and a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'
)
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by a.id desc;
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 = 1068 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262
SELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;
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 = 882 and sa.provider = 'hubspot';
select * from crm_layouts where crm_configuration_id = 834;
select * from crm_layout_entities where crm_layout_id = 2780;
select * from crm_fields where id IN (321153,321192,321193,321194);
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
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 = 1057 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8
SELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20
SELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10
SELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #
SELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;
select * from users where team_id = 51; # 7783
SELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130
select * from activity_searches where user_id = 7783;
select * from activity_search_filters where activity_search_id IN (32291, 32292);
SELECT asf.activity_search_id, asf.id, asf.value
FROM activity_search_filters asf
WHERE asf.filter = 'group_id'
AND asf.value IN (
SELECT CONCAT(
HEX(SUBSTR(uuid, 5, 4)), '-',
HEX(SUBSTR(uuid, 3, 2)), '-',
HEX(SUBSTR(uuid, 1, 2)), '-',
HEX(SUBSTR(uuid, 9, 2)), '-',
HEX(SUBSTR(uuid, 11))
)
FROM groups
WHERE deleted_at IS NOT NULL
);
SELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where provider = 'hubspot';
SELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133
SELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null
# [PASSWORD_DOTS]
select * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';
select
cp.*
# DISTINCT t.id
# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields
FROM crm_profiles cp
JOIN crm_configurations crm on crm.id = cp.crm_configuration_id
JOIN users u on u.id = cp.user_id
JOIN teams t ON t.id = crm.team_id
WHERE crm.provider = 'salesforce' and t.status = 'active'
and cp.archived_at IS NULL and u.deleted_at IS NULL
and t.id NOT IN (1093)
and t.id = 2
and cp.contact_fields IS NULL;
# and c.crm_provider_id = '003Uu00000ojD4NIAU';
SELECT * FROM users WHERE id = 26484;
SELECT * FROM crm_profiles WHERE user_id = 26484;
SELECT * FROM social_accounts WHERE sociable_id = 26484;
SELECT * FROM crm_configurations where provider = 'salesforce';
select * from users where id IN (10022, 10403);
select * from users where team_id IN (526);
select * from teams where id IN (526, 532);
select * from crm_configurations where id IN (500, 516);
select * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);
select * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';
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 = 526 and sa.provider = 'salesforce';
select * from team_settings where team_id IN (526, 532);
select * from users where id IN (22824);
select * from crm_profiles where crm_configuration_id IN (1026);
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 = 1093 and sa.provider = 'salesforce';
select * from teams where id = 1099;
select * from users where id = 29643
select * from activity_processing_states;
SELECT * FROM teams where name LIKE '%Fare%'; # 233
SELECT * FROM opportunities where crm_configuration_id = 215
# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'
;
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 = 1088 and sa.provider = 'hubspot';
SELECT * FROM teams order by updated_at DESC
SELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account
select * from crm_configurations where provider = 'pipedrive';
select * from teams where id = 957;
select * from crm_configurations where id = 957;
SELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743
SELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;
select * from users where team_id = 1; # 26726 - Gabriela Dureva
SELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific
select * from activities where user_id = 26726 order by id desc;
select * from contacts where crm_configuration_id = 1
and email IN ('[EMAIL]', '[EMAIL]'); # 2094416, 2093620
SELECT * FROM contacts WHERE id = 6284931;
SELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id
WHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;
select * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);
select * from crm_configurations where id = 1;
43801692-1aeb-32ce-acba-5b80a479701a
44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b
405975c0-b3d0-7aaa-821f-09d59cae6dd1
4caf848d-4bed-2299-b248-7788d41f9fca
49bedc3f-f196-eef3-89c3-dea6a3b4aa63
43420989-a09d-b8f8-9806-c8bbf7a02aac
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 = 1 and sa.provider = 'salesforce';
SELECT * FROM activities WHERE id = 75461988;
SELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;
select * from contacts where id = 17900517;
select * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id
where crm.provider != 'salesforce';
select * from users where id = 21047;
SELECT * FROM crm_configurations WHERE id = 892;
SELECT * FROM teams WHERE id = 942;
select * from opportunities where team_id = 942 order by updated_at desc;
select * from contacts where team_id = 942 order by updated_at desc;
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 = 942 and sa.provider = 'hubspot';
SELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430
SELECT * FROM crm_configurations WHERE id = 1;
SELECT * FROM teams WHERE crm_id = 1;
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 = 1 and sa.provider = 'salesforce';
select id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1
SELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430
select * from teams where id = 852;
select * from groups where id = 2286;
select * from sidekick_settings where team_id = 852;
select * from default_activity_types where team_id = 852;
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1 AND u.deleted_at IS NULL
AND u.crm_required = 1
AND u.team_id = 1
ORDER BY u.team_id;
SELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (
18481
);
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1
AND u.deleted_at IS NULL
AND u.crm_required = 1
# AND u.team_id = 1
AND p.id IS NULL -- Move this condition to WHERE clause
ORDER BY u.team_id;
SELECT * FROM opportunities WHERE id = 20002609;
select * from teams where id = 1122; # Velatir, 29953 - [EMAIL]
select * from crm_configurations where id = 1060;
select * from crm_layouts where crm_configuration_id = 1060;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;
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 = 1122 and sa.provider = 'hubspot';
select * from opportunities where team_id = 1122 order by updated_at desc;
select * from crm_field_data where object_type = 'contact';
SELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262
SELECT
CONCAT(u.id, CASE WHEN u.id = t.own...
|
85322
|
NULL
|
NULL
|
NULL
|
|
85323
|
2921
|
41
|
2026-05-28T12:18:12.513139+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970692513_m2.jpg...
|
PhpStorm
|
faVsco.js – HubspotJournalPollingService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
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
45
1
41
66
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993
SELECT * FROM users WHERE id = 25061;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 994;
SELECT * FROM crm_profiles WHERE user_id = 25061;
select * from crm_configurations where id = 834;
SELECT * FROM teams WHERE id = 882;
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 = 882 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;
SELECT * FROM contacts where crm_configuration_id = 834;
SELECT * FROM opportunities WHERE team_id = 933
# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');
AND id IN (8482561,18352941,19042734,19232139,19445140,19472541);
SELECT * FROM opportunity_contacts
WHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 485; #
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
select crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id
where crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')
# and l.converted_at IS NOT NULL
;
# [PASSWORD_DOTS]
SELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')
and opportunity_id IS NULL
order by id desc;
SELECT * FROM teams WHERE id = 604; # 598
SELECT * FROM activities WHERE id = 74410828; # [EMAIL]
SELECT * FROM accounts WHERE id = 20068382;
SELECT * FROM accounts WHERE id = 35186038;
SELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;
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 = 559 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;
select * from sidekick_settings where team_id = 781;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100
SELECT * FROM crm_layouts WHERE crm_configuration_id = 711;
SELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL
and is_internal = 0 and status = 'completed'
order by id desc;
SELECT * FROM crm_layout_entities
WHERE crm_layout_id IN (2352, 2353);
;
SELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;
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 = 556 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;
SELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;
select * from contacts
where crm_configuration_id = 530
and crm_provider_id = 872252;
select * from activities where crm_configuration_id = 530
and user_id = 14343 and type like '%softphone%'
and created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);
SELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t
JOIN crm_configurations c ON t.id = c.team_id
WHERE t.status = 'active';
SELECT * FROM teams where id = 1091;
SELECT * FROM crm_configurations where team_id = 1091;
SELECT * FROM activity_providers where team_id = 1091;
SELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT * FROM teams WHERE name LIKE '%Leadventure%';
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 = 1091 and sa.provider = 'salesforce';
SELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812
SELECT * FROM teams where id = 862;
SELECT * FROM crm_configurations where team_id = 862;
SELECT * FROM activity_providers where team_id = 862;
SELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT t.id, crm.id, crm.provider, ap.* FROM teams t
join crm_configurations crm on t.id = crm.team_id
join activity_providers ap on t.id = ap.team_id
where t.status = 'active' and ap.is_enabled = 1
and crm.provider = 'hubspot'
and ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',
'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');
SELECT * FROM teams where id = 1068;
SELECT * FROM crm_configurations where team_id = 1068;
SELECT * FROM activity_providers where team_id = 1068;
SELECT * FROM activities a
where crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')
and a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'
)
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by a.id desc;
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 = 1068 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262
SELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;
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 = 882 and sa.provider = 'hubspot';
select * from crm_layouts where crm_configuration_id = 834;
select * from crm_layout_entities where crm_layout_id = 2780;
select * from crm_fields where id IN (321153,321192,321193,321194);
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
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 = 1057 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8
SELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20
SELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10
SELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #
SELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;
select * from users where team_id = 51; # 7783
SELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130
select * from activity_searches where user_id = 7783;
select * from activity_search_filters where activity_search_id IN (32291, 32292);
SELECT asf.activity_search_id, asf.id, asf.value
FROM activity_search_filters asf
WHERE asf.filter = 'group_id'
AND asf.value IN (
SELECT CONCAT(
HEX(SUBSTR(uuid, 5, 4)), '-',
HEX(SUBSTR(uuid, 3, 2)), '-',
HEX(SUBSTR(uuid, 1, 2)), '-',
HEX(SUBSTR(uuid, 9, 2)), '-',
HEX(SUBSTR(uuid, 11))
)
FROM groups
WHERE deleted_at IS NOT NULL
);
SELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where provider = 'hubspot';
SELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133
SELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null
# [PASSWORD_DOTS]
select * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';
select
cp.*
# DISTINCT t.id
# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields
FROM crm_profiles cp
JOIN crm_configurations crm on crm.id = cp.crm_configuration_id
JOIN users u on u.id = cp.user_id
JOIN teams t ON t.id = crm.team_id
WHERE crm.provider = 'salesforce' and t.status = 'active'
and cp.archived_at IS NULL and u.deleted_at IS NULL
and t.id NOT IN (1093)
and t.id = 2
and cp.contact_fields IS NULL;
# and c.crm_provider_id = '003Uu00000ojD4NIAU';
SELECT * FROM users WHERE id = 26484;
SELECT * FROM crm_profiles WHERE user_id = 26484;
SELECT * FROM social_accounts WHERE sociable_id = 26484;
SELECT * FROM crm_configurations where provider = 'salesforce';
select * from users where id IN (10022, 10403);
select * from users where team_id IN (526);
select * from teams where id IN (526, 532);
select * from crm_configurations where id IN (500, 516);
select * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);
select * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';
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 = 526 and sa.provider = 'salesforce';
select * from team_settings where team_id IN (526, 532);
select * from users where id IN (22824);
select * from crm_profiles where crm_configuration_id IN (1026);
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 = 1093 and sa.provider = 'salesforce';
select * from teams where id = 1099;
select * from users where id = 29643
select * from activity_processing_states;
SELECT * FROM teams where name LIKE '%Fare%'; # 233
SELECT * FROM opportunities where crm_configuration_id = 215
# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'
;
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 = 1088 and sa.provider = 'hubspot';
SELECT * FROM teams order by updated_at DESC
SELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account
select * from crm_configurations where provider = 'pipedrive';
select * from teams where id = 957;
select * from crm_configurations where id = 957;
SELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743
SELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;
select * from users where team_id = 1; # 26726 - Gabriela Dureva
SELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific
select * from activities where user_id = 26726 order by id desc;
select * from contacts where crm_configuration_id = 1
and email IN ('[EMAIL]', '[EMAIL]'); # 2094416, 2093620
SELECT * FROM contacts WHERE id = 6284931;
SELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id
WHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;
select * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);
select * from crm_configurations where id = 1;
43801692-1aeb-32ce-acba-5b80a479701a
44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b
405975c0-b3d0-7aaa-821f-09d59cae6dd1
4caf848d-4bed-2299-b248-7788d41f9fca
49bedc3f-f196-eef3-89c3-dea6a3b4aa63
43420989-a09d-b8f8-9806-c8bbf7a02aac
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 = 1 and sa.provider = 'salesforce';
SELECT * FROM activities WHERE id = 75461988;
SELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;
select * from contacts where id = 17900517;
select * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id
where crm.provider != 'salesforce';
select * from users where id = 21047;
SELECT * FROM crm_configurations WHERE id = 892;
SELECT * FROM teams WHERE id = 942;
select * from opportunities where team_id = 942 order by updated_at desc;
select * from contacts where team_id = 942 order by updated_at desc;
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 = 942 and sa.provider = 'hubspot';
SELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430
SELECT * FROM crm_configurations WHERE id = 1;
SELECT * FROM teams WHERE crm_id = 1;
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 = 1 and sa.provider = 'salesforce';
select id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1
SELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430
select * from teams where id = 852;
select * from groups where id = 2286;
select * from sidekick_settings where team_id = 852;
select * from default_activity_types where team_id = 852;
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1 AND u.deleted_at IS NULL
AND u.crm_required = 1
AND u.team_id = 1
ORDER BY u.team_id;
SELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (
18481
);
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1
AND u.deleted_at IS NULL
AND u.crm_required = 1
# AND u.team_id = 1
AND p.id IS NULL -- Move this condition to WHERE clause
ORDER BY u.team_id;
SELECT * FROM opportunities WHERE id = 20002609;
select * from teams where id = 1122; # Velatir, 29953 - [EMAIL]
select * from crm_configurations where id = 1060;
select * from crm_layouts where crm_configuration_id = 1060;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;
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 = 1122 and sa.provider = 'hubspot';
select * from opportunities where team_id = 1122 order by updated_at desc;
select * from crm_field_data where object_type = 'contact';
SELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262
SELECT
CONCAT(u.id, CASE WHEN u.id = t.own...
|
[{"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":"#12121 on JY-20963-fix-import-on-deleted-entity, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.11569149,"height":0.025538707},"on_screen":true,"help_text":"Pull request #12121 exists for current branch JY-20963-fix-import-on-deleted-entity","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.8374335,"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":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"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 'TextRelayServiceTest'","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 'TextRelayServiceTest'","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":"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":"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":"AXStaticText","text":"12","depth":4,"bounds":{"left":0.37865692,"top":0.15003991,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"9","depth":4,"bounds":{"left":0.39029256,"top":0.15003991,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39993352,"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.40724733,"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\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.41589096,"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.4245346,"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.43550533,"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.44414893,"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.45279256,"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.4637633,"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.47473404,"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.5013298,"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.51230055,"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.7237367,"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":"45","depth":4,"bounds":{"left":0.6938165,"top":0.123703115,"width":0.010305851,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.70611703,"top":0.123703115,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"41","depth":4,"bounds":{"left":0.71542555,"top":0.123703115,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"66","depth":4,"bounds":{"left":0.72706115,"top":0.123703115,"width":0.010305851,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.7390292,"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.7463431,"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":"SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993\nSELECT * FROM users WHERE id = 25061;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 994;\nSELECT * FROM crm_profiles WHERE user_id = 25061;\n\nselect * from crm_configurations where id = 834;\nSELECT * FROM teams WHERE id = 882;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;\n\nSELECT * FROM contacts where crm_configuration_id = 834;\nSELECT * FROM opportunities WHERE team_id = 933\n# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');\nAND id IN (8482561,18352941,19042734,19232139,19445140,19472541);\nSELECT * FROM opportunity_contacts\nWHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 485; #\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nselect crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id\nwhere crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')\n# and l.converted_at IS NOT NULL\n;\n\n# ********************************************************************\nSELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')\nand opportunity_id IS NULL\norder by id desc;\n\nSELECT * FROM teams WHERE id = 604; # 598\nSELECT * FROM activities WHERE id = 74410828; # chelseaw@allvoices.co\nSELECT * FROM accounts WHERE id = 20068382;\nSELECT * FROM accounts WHERE id = 35186038;\n\nSELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 559 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;\nselect * from sidekick_settings where team_id = 781;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100\n\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 711;\nSELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL\nand is_internal = 0 and status = 'completed'\norder by id desc;\n\nSELECT * FROM crm_layout_entities\nWHERE crm_layout_id IN (2352, 2353);\n;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 556 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;\nSELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;\nselect * from contacts\nwhere crm_configuration_id = 530\nand crm_provider_id = 872252;\n\nselect * from activities where crm_configuration_id = 530\nand user_id = 14343 and type like '%softphone%'\nand created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);\n\n\nSELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t\nJOIN crm_configurations c ON t.id = c.team_id\nWHERE t.status = 'active';\n\nSELECT * FROM teams where id = 1091;\nSELECT * FROM crm_configurations where team_id = 1091;\nSELECT * FROM activity_providers where team_id = 1091;\nSELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT * FROM teams WHERE name LIKE '%Leadventure%';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1091 and sa.provider = 'salesforce';\n\nSELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812\nSELECT * FROM teams where id = 862;\nSELECT * FROM crm_configurations where team_id = 862;\nSELECT * FROM activity_providers where team_id = 862;\nSELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT t.id, crm.id, crm.provider, ap.* FROM teams t\njoin crm_configurations crm on t.id = crm.team_id\njoin activity_providers ap on t.id = ap.team_id\nwhere t.status = 'active' and ap.is_enabled = 1\nand crm.provider = 'hubspot'\nand ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',\n 'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');\n\nSELECT * FROM teams where id = 1068;\nSELECT * FROM crm_configurations where team_id = 1068;\nSELECT * FROM activity_providers where team_id = 1068;\n\nSELECT * FROM activities a\nwhere crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')\nand a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'\n )\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by a.id desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1068 and sa.provider = 'hubspot';\n\n# ********************************************************************\n# ********************************************************************\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262\nSELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\nselect * from crm_layouts where crm_configuration_id = 834;\nselect * from crm_layout_entities where crm_layout_id = 2780;\nselect * from crm_fields where id IN (321153,321192,321193,321194);\n\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1057 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8\n\nSELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20\n\nSELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10\n\nSELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #\n\nSELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;\nselect * from users where team_id = 51; # 7783\nSELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130\nselect * from activity_searches where user_id = 7783;\nselect * from activity_search_filters where activity_search_id IN (32291, 32292);\n\nSELECT asf.activity_search_id, asf.id, asf.value\nFROM activity_search_filters asf\nWHERE asf.filter = 'group_id'\nAND asf.value IN (\n SELECT CONCAT(\n HEX(SUBSTR(uuid, 5, 4)), '-',\n HEX(SUBSTR(uuid, 3, 2)), '-',\n HEX(SUBSTR(uuid, 1, 2)), '-',\n HEX(SUBSTR(uuid, 9, 2)), '-',\n HEX(SUBSTR(uuid, 11))\n )\n FROM groups\n WHERE deleted_at IS NOT NULL\n);\n\nSELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th\n# ********************************************************************\nSELECT * FROM crm_configurations where provider = 'hubspot';\nSELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133\nSELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null\n# ********************************************************************\n\nselect * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';\nselect\n cp.*\n# DISTINCT t.id\n# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields\nFROM crm_profiles cp\nJOIN crm_configurations crm on crm.id = cp.crm_configuration_id\nJOIN users u on u.id = cp.user_id\nJOIN teams t ON t.id = crm.team_id\nWHERE crm.provider = 'salesforce' and t.status = 'active'\n and cp.archived_at IS NULL and u.deleted_at IS NULL\n and t.id NOT IN (1093)\n and t.id = 2\n and cp.contact_fields IS NULL;\n# and c.crm_provider_id = '003Uu00000ojD4NIAU';\n\nSELECT * FROM users WHERE id = 26484;\nSELECT * FROM crm_profiles WHERE user_id = 26484;\nSELECT * FROM social_accounts WHERE sociable_id = 26484;\nSELECT * FROM crm_configurations where provider = 'salesforce';\nselect * from users where id IN (10022, 10403);\nselect * from users where team_id IN (526);\nselect * from teams where id IN (526, 532);\nselect * from crm_configurations where id IN (500, 516);\nselect * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);\nselect * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 526 and sa.provider = 'salesforce';\nselect * from team_settings where team_id IN (526, 532);\n\nselect * from users where id IN (22824);\nselect * from crm_profiles where crm_configuration_id IN (1026);\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1093 and sa.provider = 'salesforce';\n\nselect * from teams where id = 1099;\nselect * from users where id = 29643\n\nselect * from activity_processing_states;\n\nSELECT * FROM teams where name LIKE '%Fare%'; # 233\nSELECT * FROM opportunities where crm_configuration_id = 215\n# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1088 and sa.provider = 'hubspot';\n\nSELECT * FROM teams order by updated_at DESC\nSELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account\n\nselect * from crm_configurations where provider = 'pipedrive';\n\nselect * from teams where id = 957;\nselect * from crm_configurations where id = 957;\n\nSELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743\nSELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;\n\nselect * from users where team_id = 1; # 26726 - Gabriela Dureva\nSELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific\nselect * from activities where user_id = 26726 order by id desc;\nselect * from contacts where crm_configuration_id = 1\nand email IN ('charlotte.ward@prolific.com', 'frankie.bryant@prolific.com'); # 2094416, 2093620\nSELECT * FROM contacts WHERE id = 6284931;\n\nSELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id\nWHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;\n\nselect * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);\nselect * from crm_configurations where id = 1;\n\n43801692-1aeb-32ce-acba-5b80a479701a\n44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b\n405975c0-b3d0-7aaa-821f-09d59cae6dd1\n4caf848d-4bed-2299-b248-7788d41f9fca\n49bedc3f-f196-eef3-89c3-dea6a3b4aa63\n43420989-a09d-b8f8-9806-c8bbf7a02aac\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nSELECT * FROM activities WHERE id = 75461988;\n\nSELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;\n\nselect * from contacts where id = 17900517;\n\nselect * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id\nwhere crm.provider != 'salesforce';\n\nselect * from users where id = 21047;\nSELECT * FROM crm_configurations WHERE id = 892;\nSELECT * FROM teams WHERE id = 942;\nselect * from opportunities where team_id = 942 order by updated_at desc;\nselect * from contacts where team_id = 942 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 942 and sa.provider = 'hubspot';\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430\nSELECT * FROM crm_configurations WHERE id = 1;\nSELECT * FROM teams WHERE crm_id = 1;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nselect id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1\nSELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430\n\nselect * from teams where id = 852;\nselect * from groups where id = 2286;\nselect * from sidekick_settings where team_id = 852;\nselect * from default_activity_types where team_id = 852;\n\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1 AND u.deleted_at IS NULL\nAND u.crm_required = 1\nAND u.team_id = 1\nORDER BY u.team_id;\n\nSELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (\n18481\n );\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1\n AND u.deleted_at IS NULL\n AND u.crm_required = 1\n# AND u.team_id = 1\n AND p.id IS NULL -- Move this condition to WHERE clause\nORDER BY u.team_id;\n\nSELECT * FROM opportunities WHERE id = 20002609;\nselect * from teams where id = 1122; # Velatir, 29953 - christian@velatir.com\nselect * from crm_configurations where id = 1060;\nselect * from crm_layouts where crm_configuration_id = 1060;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1122 and sa.provider = 'hubspot';\nselect * from opportunities where team_id = 1122 order by updated_at desc;\n\nselect * from crm_field_data where object_type = 'contact';\n\nSELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 248 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS\nSELECT * FROM users where id = 24115;\nSELECT * FROM accounts where id = 4002896;\nSELECT * FROM teams WHERE name LIKE '%adswerve%';\nSELECT * FROM opportunities where crm_configuration_id = 230 AND crm_provider_id IN (\"0069N000003GIQ9QAO\",\"0061r000019yGP9AAM\",\"0066900001S2KWlAAN\",\"0066900001TDpj2AAD\",\"0066900001b8uEwAAI\",\"0069N000001rQi0QAE\",\"006QF00000KD40mYAD\",\"006QF00000LzpRJYAZ\",\"0069N000002uomtQAA\",\"0069N000002xlMLQAY\",\"0066900001NV6ubAAD\",\"0061r00001HJp45AAD\",\"006QF00000uTlUoYAK\",\"006QF00000v0bZqYAI\");\nSELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203\n\nSELECT u.id, u.email, ac.name, a.* FROM activities a\nJOIN users u ON a.user_id = u.id\nJOIN accounts ac ON a.account_id = ac.id\nWHERE\nuuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or\nuuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or\nuuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;\n\nselect * from users where id = 5825;\nSELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;\n\nselect * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;\n19594, 862\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 862 and sa.provider = 'salesforce';\n\nselect * from automated_reports where id = 36;\nselect ar.frequency, r.*, ar.* from automated_report_results r\njoin automated_reports ar on r.report_id = ar.id\nwhere ar.frequency != 'one_off';\n\nselect s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;\nselect * from nudges n where n.activity_search_id\n\nselect * from teams where created_at > '2026-03-09';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;\n\nselect * from users where team_id = 1 and name like '%Lukas%'; # 7160\n\nSELECT * FROM teams WHERE id = 575;\nselect * from opportunities where team_id = 575;\nSELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,\nselect * from opportunities where team_id = 1126;\nSELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,\nselect * from opportunities where team_id = 1125;\nselect * from contacts c\nwhere c.team_id = 882;\n\nSELECT * FROM activities WHERE id = 76822967;\nSELECT * FROM crm_profiles WHERE user_id = 15440;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 555;\nSELECT * FROM crm_configurations WHERE id = 555;\nSELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 581 and sa.provider = 'salesforce';\n\nSELECT * FROM automated_report_results order by id desc;\n\nselect * from features;\nselect * from team_features where feature_id = 40;\n\nselect * from teams where id = 556;\n\nselect * from automated_reports;\nwhere id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , [\"pdf\",\"podcast\"]\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\nselect * from automated_report_results order by id desc;\nSELECT * FROM automated_report_results WHERE id = 1919;\n\nselect * from automated_report_results WHERE report_id = 54;\n\nselect * from opportunities where id = 7594349;\n\nSELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067 - jiminnyintegration@lesmills.com\nselect * from playbooks where team_id = 711; # event 226147\nSELECT * FROM playbook_categories WHERE playbook_id = 5515;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event';\nSELECT * FROM crm_fields WHERE id = 226147;\nSELECT * FROM crm_field_values WHERE crm_field_id = 226147;\n\nSELECT * FROM crm_configurations WHERE id = 692;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 711 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles cp JOIN users u on u.id = cp.user_id WHERE u.team_id = 711;\n\nselect * from leads;\n\nselect * from calendars;\n\nSELECT\n t.id AS team_id,\n t.name,\n LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1)) AS calendar_domain\nFROM teams t\nJOIN users u ON u.team_id = t.id\nJOIN calendars c ON c.user_id = u.id AND c.status = 'active' AND c.calendar_provider_id LIKE '%@%'\nLEFT JOIN team_domains td\n ON td.team_id = t.id\n AND td.deleted_at IS NULL\n AND td.domain = LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1))\nGROUP BY t.id, t.name, calendar_domain\nORDER BY t.name, calendar_domain;\n\nselect * from users u join calendars c on c.user_id = u.id\nwhere u.team_id = 882;\n\n\nselect * from activities where id = 74049485; # team 563 crm 537\nselect * from activities where id = 73272382; # team 563 crm 537\nselect * from activities where id = 64400389; # team 563 crm 537\nselect * from activities where id = 58081273; # team 563 crm 537\nselect * from activities where id = 54520297; # team 563 crm 537\nselect * from participants where activity_id = 58081273;\n\nselect * from activities where crm_configuration_id = 537 and provider = 'aircall'\nand account_id = 19003658 order by updated_at desc;\n\nselect * from contacts where crm_configuration_id = 537 and id = 35957759;\nselect * from accounts where crm_configuration_id = 537 and id = 19003658;\n\nselect * from automated_report_results where id = 1976;\nselect * from automated_reports where id = 583;\nselect * from activity_searches where id = 87714;\nselect * from activity_search_filters where activity_search_id = 87714;\n\nSELECT * FROM activities WHERE uuid_to_bin('8827f672-202d-4162-9d04-73ff5f0566a9') = uuid\nor uuid_to_bin('47842446-af51-4bcb-854f-cc6560290101') = uuid;\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot';\nselect * from rate_limits;\n\nselect * from automated_report_results where media_type = 'pdf' and status = 2\nand id IN (18, 1872);\nselect * from automated_reports where id = 54;\nSELECT * FROM users WHERE id IN (24623,29443,29613);\n\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\n\nselect * from text_relays where created_at > '2026-05-01';\nand id IN (32415, 32416);\n# and id = 32412;\n\nselect * from users where team_id = 2 and email like '%scott%' and id = 29510;\n\nSELECT * FROM activities WHERE uuid_to_bin('67cebfc2-ed56-44a2-8c68-7a0286ed8618') = uuid; # 79763436\n\nSELECT email_provider_id, COUNT(*) as count, GROUP_CONCAT(id) as ids, GROUP_CONCAT(status) as statuses\nFROM text_relays\nWHERE email_provider_id IN ('19e2027868a64b42', '19e2033ed8ea6b10')\nGROUP BY email_provider_id;\nSELECT id, status, telephony_provider_id, created_at\nFROM activities\nWHERE id IN (80028719, 80028846);\nSELECT id, status, code, email_sent_at, created_at, updated_at\nFROM text_relays\nWHERE id IN (32415, 32416);\nSELECT id, status, code, sender, recipient, created_at\nFROM text_relays\nWHERE sender LIKE '%mario.georgiev%' OR sender LIKE '%stoyan.tomov%'\nORDER BY created_at DESC\nLIMIT 10;\n\nSELECT id, uuid, status, code, sender, recipient, created_at, updated_at\nFROM text_relays\nWHERE uuid = uuid_to_bin('0626141c-27a6-4d8c-aff8-7c8020a2c656');\n\n# ***************\nSELECT DISTINCT u.id, u.email, u.name, u.softphone_number, COUNT(a.id) as sms_count\nFROM users u\nINNER JOIN activities a ON u.id = .user_id\nWHERE a.type LIKE 'sms%'\nAND a.created_at > DATE_SUB(NOW(), INTERVAL 30 DAY)\nGROUP BY u.id, u.email, u.name, u.softphone_number\nORDER BY sms_count DESC;\n\nselect * from teams where id = 1;\n\nselect * from roles;\n\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1117 and sa.provider = 'hubspot';\nSELECT * FROM activities WHERE uuid_to_bin('8024fffb-2df7-4017-91f4-d9f896050248') = uuid; # 79933459 YES\nSELECT * FROM activities WHERE uuid_to_bin('25529043-8094-4781-927f-4f4da2a8185c') = uuid; # 80186192 NO\nSELECT * FROM crm_configurations WHERE id = 1053;\nSELECT * FROM teams WHERE id = 1117;\nselect * from users where id = 30249;\nselect * from playbooks where id = 5473;\nselect * from playbook_categories where id = 43783;\nselect * from playbook_categories where playbook_id = 5473;\nselect * from crm_fields where id = 659242;\nselect * from crm_field_values where crm_field_id = 659242;\n\nSELECT * FROM crm_field_data fd\n# JOIN crm_fields f ON fd.crm_field_id = f.id\n# JOIN activities a ON fd.activity_id = a.id\nWHERE activity_id = 79933459\n# AND f.crm_provider_id = 'hs_activity_type';\n\n\nSELECT * FROM activity_messages;\nselect * from text_relays where created_at > '2026-05-01';\nselect * from activities where user_id IN (7160, 18608) and created_at > '2026-05-22' order by id desc;\nselect * from users where team_id = 1 and id IN (18608, 13934, 7160);\nselect * from activities where user_id = 7160 order by id desc limit 10;\n\nselect * from accounts where team_id = 1 and name = 'Column5';\n\nselect * from users where name like '%Subra%'; # 31054, 1117\nselect * from teams where id = 1117;\nselect * from activity_searches where user_id = 31054;\nselect * from activity_search_filters where activity_search_id IN (88882, 88902);","depth":4,"on_screen":true,"value":"SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993\nSELECT * FROM users WHERE id = 25061;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 994;\nSELECT * FROM crm_profiles WHERE user_id = 25061;\n\nselect * from crm_configurations where id = 834;\nSELECT * FROM teams WHERE id = 882;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;\n\nSELECT * FROM contacts where crm_configuration_id = 834;\nSELECT * FROM opportunities WHERE team_id = 933\n# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');\nAND id IN (8482561,18352941,19042734,19232139,19445140,19472541);\nSELECT * FROM opportunity_contacts\nWHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 485; #\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nselect crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id\nwhere crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')\n# and l.converted_at IS NOT NULL\n;\n\n# ********************************************************************\nSELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')\nand opportunity_id IS NULL\norder by id desc;\n\nSELECT * FROM teams WHERE id = 604; # 598\nSELECT * FROM activities WHERE id = 74410828; # chelseaw@allvoices.co\nSELECT * FROM accounts WHERE id = 20068382;\nSELECT * FROM accounts WHERE id = 35186038;\n\nSELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 559 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;\nselect * from sidekick_settings where team_id = 781;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100\n\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 711;\nSELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL\nand is_internal = 0 and status = 'completed'\norder by id desc;\n\nSELECT * FROM crm_layout_entities\nWHERE crm_layout_id IN (2352, 2353);\n;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 556 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;\nSELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;\nselect * from contacts\nwhere crm_configuration_id = 530\nand crm_provider_id = 872252;\n\nselect * from activities where crm_configuration_id = 530\nand user_id = 14343 and type like '%softphone%'\nand created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);\n\n\nSELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t\nJOIN crm_configurations c ON t.id = c.team_id\nWHERE t.status = 'active';\n\nSELECT * FROM teams where id = 1091;\nSELECT * FROM crm_configurations where team_id = 1091;\nSELECT * FROM activity_providers where team_id = 1091;\nSELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT * FROM teams WHERE name LIKE '%Leadventure%';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1091 and sa.provider = 'salesforce';\n\nSELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812\nSELECT * FROM teams where id = 862;\nSELECT * FROM crm_configurations where team_id = 862;\nSELECT * FROM activity_providers where team_id = 862;\nSELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT t.id, crm.id, crm.provider, ap.* FROM teams t\njoin crm_configurations crm on t.id = crm.team_id\njoin activity_providers ap on t.id = ap.team_id\nwhere t.status = 'active' and ap.is_enabled = 1\nand crm.provider = 'hubspot'\nand ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',\n 'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');\n\nSELECT * FROM teams where id = 1068;\nSELECT * FROM crm_configurations where team_id = 1068;\nSELECT * FROM activity_providers where team_id = 1068;\n\nSELECT * FROM activities a\nwhere crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')\nand a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'\n )\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by a.id desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1068 and sa.provider = 'hubspot';\n\n# ********************************************************************\n# ********************************************************************\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262\nSELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\nselect * from crm_layouts where crm_configuration_id = 834;\nselect * from crm_layout_entities where crm_layout_id = 2780;\nselect * from crm_fields where id IN (321153,321192,321193,321194);\n\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1057 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8\n\nSELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20\n\nSELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10\n\nSELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #\n\nSELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;\nselect * from users where team_id = 51; # 7783\nSELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130\nselect * from activity_searches where user_id = 7783;\nselect * from activity_search_filters where activity_search_id IN (32291, 32292);\n\nSELECT asf.activity_search_id, asf.id, asf.value\nFROM activity_search_filters asf\nWHERE asf.filter = 'group_id'\nAND asf.value IN (\n SELECT CONCAT(\n HEX(SUBSTR(uuid, 5, 4)), '-',\n HEX(SUBSTR(uuid, 3, 2)), '-',\n HEX(SUBSTR(uuid, 1, 2)), '-',\n HEX(SUBSTR(uuid, 9, 2)), '-',\n HEX(SUBSTR(uuid, 11))\n )\n FROM groups\n WHERE deleted_at IS NOT NULL\n);\n\nSELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th\n# ********************************************************************\nSELECT * FROM crm_configurations where provider = 'hubspot';\nSELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133\nSELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null\n# ********************************************************************\n\nselect * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';\nselect\n cp.*\n# DISTINCT t.id\n# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields\nFROM crm_profiles cp\nJOIN crm_configurations crm on crm.id = cp.crm_configuration_id\nJOIN users u on u.id = cp.user_id\nJOIN teams t ON t.id = crm.team_id\nWHERE crm.provider = 'salesforce' and t.status = 'active'\n and cp.archived_at IS NULL and u.deleted_at IS NULL\n and t.id NOT IN (1093)\n and t.id = 2\n and cp.contact_fields IS NULL;\n# and c.crm_provider_id = '003Uu00000ojD4NIAU';\n\nSELECT * FROM users WHERE id = 26484;\nSELECT * FROM crm_profiles WHERE user_id = 26484;\nSELECT * FROM social_accounts WHERE sociable_id = 26484;\nSELECT * FROM crm_configurations where provider = 'salesforce';\nselect * from users where id IN (10022, 10403);\nselect * from users where team_id IN (526);\nselect * from teams where id IN (526, 532);\nselect * from crm_configurations where id IN (500, 516);\nselect * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);\nselect * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 526 and sa.provider = 'salesforce';\nselect * from team_settings where team_id IN (526, 532);\n\nselect * from users where id IN (22824);\nselect * from crm_profiles where crm_configuration_id IN (1026);\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1093 and sa.provider = 'salesforce';\n\nselect * from teams where id = 1099;\nselect * from users where id = 29643\n\nselect * from activity_processing_states;\n\nSELECT * FROM teams where name LIKE '%Fare%'; # 233\nSELECT * FROM opportunities where crm_configuration_id = 215\n# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1088 and sa.provider = 'hubspot';\n\nSELECT * FROM teams order by updated_at DESC\nSELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account\n\nselect * from crm_configurations where provider = 'pipedrive';\n\nselect * from teams where id = 957;\nselect * from crm_configurations where id = 957;\n\nSELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743\nSELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;\n\nselect * from users where team_id = 1; # 26726 - Gabriela Dureva\nSELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific\nselect * from activities where user_id = 26726 order by id desc;\nselect * from contacts where crm_configuration_id = 1\nand email IN ('charlotte.ward@prolific.com', 'frankie.bryant@prolific.com'); # 2094416, 2093620\nSELECT * FROM contacts WHERE id = 6284931;\n\nSELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id\nWHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;\n\nselect * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);\nselect * from crm_configurations where id = 1;\n\n43801692-1aeb-32ce-acba-5b80a479701a\n44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b\n405975c0-b3d0-7aaa-821f-09d59cae6dd1\n4caf848d-4bed-2299-b248-7788d41f9fca\n49bedc3f-f196-eef3-89c3-dea6a3b4aa63\n43420989-a09d-b8f8-9806-c8bbf7a02aac\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nSELECT * FROM activities WHERE id = 75461988;\n\nSELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;\n\nselect * from contacts where id = 17900517;\n\nselect * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id\nwhere crm.provider != 'salesforce';\n\nselect * from users where id = 21047;\nSELECT * FROM crm_configurations WHERE id = 892;\nSELECT * FROM teams WHERE id = 942;\nselect * from opportunities where team_id = 942 order by updated_at desc;\nselect * from contacts where team_id = 942 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 942 and sa.provider = 'hubspot';\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430\nSELECT * FROM crm_configurations WHERE id = 1;\nSELECT * FROM teams WHERE crm_id = 1;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nselect id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1\nSELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430\n\nselect * from teams where id = 852;\nselect * from groups where id = 2286;\nselect * from sidekick_settings where team_id = 852;\nselect * from default_activity_types where team_id = 852;\n\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1 AND u.deleted_at IS NULL\nAND u.crm_required = 1\nAND u.team_id = 1\nORDER BY u.team_id;\n\nSELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (\n18481\n );\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1\n AND u.deleted_at IS NULL\n AND u.crm_required = 1\n# AND u.team_id = 1\n AND p.id IS NULL -- Move this condition to WHERE clause\nORDER BY u.team_id;\n\nSELECT * FROM opportunities WHERE id = 20002609;\nselect * from teams where id = 1122; # Velatir, 29953 - christian@velatir.com\nselect * from crm_configurations where id = 1060;\nselect * from crm_layouts where crm_configuration_id = 1060;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1122 and sa.provider = 'hubspot';\nselect * from opportunities where team_id = 1122 order by updated_at desc;\n\nselect * from crm_field_data where object_type = 'contact';\n\nSELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 248 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS\nSELECT * FROM users where id = 24115;\nSELECT * FROM accounts where id = 4002896;\nSELECT * FROM teams WHERE name LIKE '%adswerve%';\nSELECT * FROM opportunities where crm_configuration_id = 230 AND crm_provider_id IN (\"0069N000003GIQ9QAO\",\"0061r000019yGP9AAM\",\"0066900001S2KWlAAN\",\"0066900001TDpj2AAD\",\"0066900001b8uEwAAI\",\"0069N000001rQi0QAE\",\"006QF00000KD40mYAD\",\"006QF00000LzpRJYAZ\",\"0069N000002uomtQAA\",\"0069N000002xlMLQAY\",\"0066900001NV6ubAAD\",\"0061r00001HJp45AAD\",\"006QF00000uTlUoYAK\",\"006QF00000v0bZqYAI\");\nSELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203\n\nSELECT u.id, u.email, ac.name, a.* FROM activities a\nJOIN users u ON a.user_id = u.id\nJOIN accounts ac ON a.account_id = ac.id\nWHERE\nuuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or\nuuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or\nuuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;\n\nselect * from users where id = 5825;\nSELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;\n\nselect * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;\n19594, 862\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 862 and sa.provider = 'salesforce';\n\nselect * from automated_reports where id = 36;\nselect ar.frequency, r.*, ar.* from automated_report_results r\njoin automated_reports ar on r.report_id = ar.id\nwhere ar.frequency != 'one_off';\n\nselect s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;\nselect * from nudges n where n.activity_search_id\n\nselect * from teams where created_at > '2026-03-09';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;\n\nselect * from users where team_id = 1 and name like '%Lukas%'; # 7160\n\nSELECT * FROM teams WHERE id = 575;\nselect * from opportunities where team_id = 575;\nSELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,\nselect * from opportunities where team_id = 1126;\nSELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,\nselect * from opportunities where team_id = 1125;\nselect * from contacts c\nwhere c.team_id = 882;\n\nSELECT * FROM activities WHERE id = 76822967;\nSELECT * FROM crm_profiles WHERE user_id = 15440;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 555;\nSELECT * FROM crm_configurations WHERE id = 555;\nSELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 581 and sa.provider = 'salesforce';\n\nSELECT * FROM automated_report_results order by id desc;\n\nselect * from features;\nselect * from team_features where feature_id = 40;\n\nselect * from teams where id = 556;\n\nselect * from automated_reports;\nwhere id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , [\"pdf\",\"podcast\"]\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\nselect * from automated_report_results order by id desc;\nSELECT * FROM automated_report_results WHERE id = 1919;\n\nselect * from automated_report_results WHERE report_id = 54;\n\nselect * from opportunities where id = 7594349;\n\nSELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067 - jiminnyintegration@lesmills.com\nselect * from playbooks where team_id = 711; # event 226147\nSELECT * FROM playbook_categories WHERE playbook_id = 5515;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event';\nSELECT * FROM crm_fields WHERE id = 226147;\nSELECT * FROM crm_field_values WHERE crm_field_id = 226147;\n\nSELECT * FROM crm_configurations WHERE id = 692;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 711 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles cp JOIN users u on u.id = cp.user_id WHERE u.team_id = 711;\n\nselect * from leads;\n\nselect * from calendars;\n\nSELECT\n t.id AS team_id,\n t.name,\n LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1)) AS calendar_domain\nFROM teams t\nJOIN users u ON u.team_id = t.id\nJOIN calendars c ON c.user_id = u.id AND c.status = 'active' AND c.calendar_provider_id LIKE '%@%'\nLEFT JOIN team_domains td\n ON td.team_id = t.id\n AND td.deleted_at IS NULL\n AND td.domain = LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1))\nGROUP BY t.id, t.name, calendar_domain\nORDER BY t.name, calendar_domain;\n\nselect * from users u join calendars c on c.user_id = u.id\nwhere u.team_id = 882;\n\n\nselect * from activities where id = 74049485; # team 563 crm 537\nselect * from activities where id = 73272382; # team 563 crm 537\nselect * from activities where id = 64400389; # team 563 crm 537\nselect * from activities where id = 58081273; # team 563 crm 537\nselect * from activities where id = 54520297; # team 563 crm 537\nselect * from participants where activity_id = 58081273;\n\nselect * from activities where crm_configuration_id = 537 and provider = 'aircall'\nand account_id = 19003658 order by updated_at desc;\n\nselect * from contacts where crm_configuration_id = 537 and id = 35957759;\nselect * from accounts where crm_configuration_id = 537 and id = 19003658;\n\nselect * from automated_report_results where id = 1976;\nselect * from automated_reports where id = 583;\nselect * from activity_searches where id = 87714;\nselect * from activity_search_filters where activity_search_id = 87714;\n\nSELECT * FROM activities WHERE uuid_to_bin('8827f672-202d-4162-9d04-73ff5f0566a9') = uuid\nor uuid_to_bin('47842446-af51-4bcb-854f-cc6560290101') = uuid;\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot';\nselect * from rate_limits;\n\nselect * from automated_report_results where media_type = 'pdf' and status = 2\nand id IN (18, 1872);\nselect * from automated_reports where id = 54;\nSELECT * FROM users WHERE id IN (24623,29443,29613);\n\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\n\nselect * from text_relays where created_at > '2026-05-01';\nand id IN (32415, 32416);\n# and id = 32412;\n\nselect * from users where team_id = 2 and email like '%scott%' and id = 29510;\n\nSELECT * FROM activities WHERE uuid_to_bin('67cebfc2-ed56-44a2-8c68-7a0286ed8618') = uuid; # 79763436\n\nSELECT email_provider_id, COUNT(*) as count, GROUP_CONCAT(id) as ids, GROUP_CONCAT(status) as statuses\nFROM text_relays\nWHERE email_provider_id IN ('19e2027868a64b42', '19e2033ed8ea6b10')\nGROUP BY email_provider_id;\nSELECT id, status, telephony_provider_id, created_at\nFROM activities\nWHERE id IN (80028719, 80028846);\nSELECT id, status, code, email_sent_at, created_at, updated_at\nFROM text_relays\nWHERE id IN (32415, 32416);\nSELECT id, status, code, sender, recipient, created_at\nFROM text_relays\nWHERE sender LIKE '%mario.georgiev%' OR sender LIKE '%stoyan.tomov%'\nORDER BY created_at DESC\nLIMIT 10;\n\nSELECT id, uuid, status, code, sender, recipient, created_at, updated_at\nFROM text_relays\nWHERE uuid = uuid_to_bin('0626141c-27a6-4d8c-aff8-7c8020a2c656');\n\n# ***************\nSELECT DISTINCT u.id, u.email, u.name, u.softphone_number, COUNT(a.id) as sms_count\nFROM users u\nINNER JOIN activities a ON u.id = .user_id\nWHERE a.type LIKE 'sms%'\nAND a.created_at > DATE_SUB(NOW(), INTERVAL 30 DAY)\nGROUP BY u.id, u.email, u.name, u.softphone_number\nORDER BY sms_count DESC;\n\nselect * from teams where id = 1;\n\nselect * from roles;\n\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1117 and sa.provider = 'hubspot';\nSELECT * FROM activities WHERE uuid_to_bin('8024fffb-2df7-4017-91f4-d9f896050248') = uuid; # 79933459 YES\nSELECT * FROM activities WHERE uuid_to_bin('25529043-8094-4781-927f-4f4da2a8185c') = uuid; # 80186192 NO\nSELECT * FROM crm_configurations WHERE id = 1053;\nSELECT * FROM teams WHERE id = 1117;\nselect * from users where id = 30249;\nselect * from playbooks where id = 5473;\nselect * from playbook_categories where id = 43783;\nselect * from playbook_categories where playbook_id = 5473;\nselect * from crm_fields where id = 659242;\nselect * from crm_field_values where crm_field_id = 659242;\n\nSELECT * FROM crm_field_data fd\n# JOIN crm_fields f ON fd.crm_field_id = f.id\n# JOIN activities a ON fd.activity_id = a.id\nWHERE activity_id = 79933459\n# AND f.crm_provider_id = 'hs_activity_type';\n\n\nSELECT * FROM activity_messages;\nselect * from text_relays where created_at > '2026-05-01';\nselect * from activities where user_id IN (7160, 18608) and created_at > '2026-05-22' order by id desc;\nselect * from users where team_id = 1 and id IN (18608, 13934, 7160);\nselect * from activities where user_id = 7160 order by id desc limit 10;\n\nselect * from accounts where team_id = 1 and name = 'Column5';\n\nselect * from users where name like '%Subra%'; # 31054, 1117\nselect * from teams where id = 1117;\nselect * from activity_searches where user_id = 31054;\nselect * from activity_search_filters where activity_search_id IN (88882, 88902);","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":"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}]...
|
2526630547225287665
|
1137771414947247693
|
typing_pause
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
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
45
1
41
66
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993
SELECT * FROM users WHERE id = 25061;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 994;
SELECT * FROM crm_profiles WHERE user_id = 25061;
select * from crm_configurations where id = 834;
SELECT * FROM teams WHERE id = 882;
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 = 882 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;
SELECT * FROM contacts where crm_configuration_id = 834;
SELECT * FROM opportunities WHERE team_id = 933
# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');
AND id IN (8482561,18352941,19042734,19232139,19445140,19472541);
SELECT * FROM opportunity_contacts
WHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 485; #
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
select crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id
where crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')
# and l.converted_at IS NOT NULL
;
# [PASSWORD_DOTS]
SELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')
and opportunity_id IS NULL
order by id desc;
SELECT * FROM teams WHERE id = 604; # 598
SELECT * FROM activities WHERE id = 74410828; # [EMAIL]
SELECT * FROM accounts WHERE id = 20068382;
SELECT * FROM accounts WHERE id = 35186038;
SELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;
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 = 559 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;
select * from sidekick_settings where team_id = 781;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100
SELECT * FROM crm_layouts WHERE crm_configuration_id = 711;
SELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL
and is_internal = 0 and status = 'completed'
order by id desc;
SELECT * FROM crm_layout_entities
WHERE crm_layout_id IN (2352, 2353);
;
SELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;
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 = 556 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;
SELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;
select * from contacts
where crm_configuration_id = 530
and crm_provider_id = 872252;
select * from activities where crm_configuration_id = 530
and user_id = 14343 and type like '%softphone%'
and created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);
SELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t
JOIN crm_configurations c ON t.id = c.team_id
WHERE t.status = 'active';
SELECT * FROM teams where id = 1091;
SELECT * FROM crm_configurations where team_id = 1091;
SELECT * FROM activity_providers where team_id = 1091;
SELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT * FROM teams WHERE name LIKE '%Leadventure%';
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 = 1091 and sa.provider = 'salesforce';
SELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812
SELECT * FROM teams where id = 862;
SELECT * FROM crm_configurations where team_id = 862;
SELECT * FROM activity_providers where team_id = 862;
SELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT t.id, crm.id, crm.provider, ap.* FROM teams t
join crm_configurations crm on t.id = crm.team_id
join activity_providers ap on t.id = ap.team_id
where t.status = 'active' and ap.is_enabled = 1
and crm.provider = 'hubspot'
and ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',
'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');
SELECT * FROM teams where id = 1068;
SELECT * FROM crm_configurations where team_id = 1068;
SELECT * FROM activity_providers where team_id = 1068;
SELECT * FROM activities a
where crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')
and a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'
)
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by a.id desc;
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 = 1068 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262
SELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;
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 = 882 and sa.provider = 'hubspot';
select * from crm_layouts where crm_configuration_id = 834;
select * from crm_layout_entities where crm_layout_id = 2780;
select * from crm_fields where id IN (321153,321192,321193,321194);
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
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 = 1057 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8
SELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20
SELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10
SELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #
SELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;
select * from users where team_id = 51; # 7783
SELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130
select * from activity_searches where user_id = 7783;
select * from activity_search_filters where activity_search_id IN (32291, 32292);
SELECT asf.activity_search_id, asf.id, asf.value
FROM activity_search_filters asf
WHERE asf.filter = 'group_id'
AND asf.value IN (
SELECT CONCAT(
HEX(SUBSTR(uuid, 5, 4)), '-',
HEX(SUBSTR(uuid, 3, 2)), '-',
HEX(SUBSTR(uuid, 1, 2)), '-',
HEX(SUBSTR(uuid, 9, 2)), '-',
HEX(SUBSTR(uuid, 11))
)
FROM groups
WHERE deleted_at IS NOT NULL
);
SELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where provider = 'hubspot';
SELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133
SELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null
# [PASSWORD_DOTS]
select * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';
select
cp.*
# DISTINCT t.id
# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields
FROM crm_profiles cp
JOIN crm_configurations crm on crm.id = cp.crm_configuration_id
JOIN users u on u.id = cp.user_id
JOIN teams t ON t.id = crm.team_id
WHERE crm.provider = 'salesforce' and t.status = 'active'
and cp.archived_at IS NULL and u.deleted_at IS NULL
and t.id NOT IN (1093)
and t.id = 2
and cp.contact_fields IS NULL;
# and c.crm_provider_id = '003Uu00000ojD4NIAU';
SELECT * FROM users WHERE id = 26484;
SELECT * FROM crm_profiles WHERE user_id = 26484;
SELECT * FROM social_accounts WHERE sociable_id = 26484;
SELECT * FROM crm_configurations where provider = 'salesforce';
select * from users where id IN (10022, 10403);
select * from users where team_id IN (526);
select * from teams where id IN (526, 532);
select * from crm_configurations where id IN (500, 516);
select * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);
select * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';
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 = 526 and sa.provider = 'salesforce';
select * from team_settings where team_id IN (526, 532);
select * from users where id IN (22824);
select * from crm_profiles where crm_configuration_id IN (1026);
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 = 1093 and sa.provider = 'salesforce';
select * from teams where id = 1099;
select * from users where id = 29643
select * from activity_processing_states;
SELECT * FROM teams where name LIKE '%Fare%'; # 233
SELECT * FROM opportunities where crm_configuration_id = 215
# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'
;
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 = 1088 and sa.provider = 'hubspot';
SELECT * FROM teams order by updated_at DESC
SELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account
select * from crm_configurations where provider = 'pipedrive';
select * from teams where id = 957;
select * from crm_configurations where id = 957;
SELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743
SELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;
select * from users where team_id = 1; # 26726 - Gabriela Dureva
SELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific
select * from activities where user_id = 26726 order by id desc;
select * from contacts where crm_configuration_id = 1
and email IN ('[EMAIL]', '[EMAIL]'); # 2094416, 2093620
SELECT * FROM contacts WHERE id = 6284931;
SELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id
WHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;
select * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);
select * from crm_configurations where id = 1;
43801692-1aeb-32ce-acba-5b80a479701a
44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b
405975c0-b3d0-7aaa-821f-09d59cae6dd1
4caf848d-4bed-2299-b248-7788d41f9fca
49bedc3f-f196-eef3-89c3-dea6a3b4aa63
43420989-a09d-b8f8-9806-c8bbf7a02aac
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 = 1 and sa.provider = 'salesforce';
SELECT * FROM activities WHERE id = 75461988;
SELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;
select * from contacts where id = 17900517;
select * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id
where crm.provider != 'salesforce';
select * from users where id = 21047;
SELECT * FROM crm_configurations WHERE id = 892;
SELECT * FROM teams WHERE id = 942;
select * from opportunities where team_id = 942 order by updated_at desc;
select * from contacts where team_id = 942 order by updated_at desc;
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 = 942 and sa.provider = 'hubspot';
SELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430
SELECT * FROM crm_configurations WHERE id = 1;
SELECT * FROM teams WHERE crm_id = 1;
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 = 1 and sa.provider = 'salesforce';
select id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1
SELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430
select * from teams where id = 852;
select * from groups where id = 2286;
select * from sidekick_settings where team_id = 852;
select * from default_activity_types where team_id = 852;
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1 AND u.deleted_at IS NULL
AND u.crm_required = 1
AND u.team_id = 1
ORDER BY u.team_id;
SELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (
18481
);
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1
AND u.deleted_at IS NULL
AND u.crm_required = 1
# AND u.team_id = 1
AND p.id IS NULL -- Move this condition to WHERE clause
ORDER BY u.team_id;
SELECT * FROM opportunities WHERE id = 20002609;
select * from teams where id = 1122; # Velatir, 29953 - [EMAIL]
select * from crm_configurations where id = 1060;
select * from crm_layouts where crm_configuration_id = 1060;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;
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 = 1122 and sa.provider = 'hubspot';
select * from opportunities where team_id = 1122 order by updated_at desc;
select * from crm_field_data where object_type = 'contact';
SELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262
SELECT
CONCAT(u.id, CASE WHEN u.id = t.own...
|
85320
|
NULL
|
NULL
|
NULL
|
|
85322
|
2920
|
40
|
2026-05-28T12:18:12.412042+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970692412_m1.jpg...
|
PhpStorm
|
faVsco.js – HubspotJournalPollingService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…...
|
[{"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":"#12121 on JY-20963-fix-import-on-deleted-entity, menu","depth":5,"on_screen":true,"help_text":"Pull request #12121 exists for current branch JY-20963-fix-import-on-deleted-entity","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":"TextRelayServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","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":"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":"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":"AXStaticText","text":"12","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"9","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\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","role_description":"text entry area","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}]...
|
5244664462083781043
|
-1183062619475730196
|
typing_pause
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85321
|
2920
|
39
|
2026-05-28T12:18:08.961395+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970688961_m1.jpg...
|
PhpStorm
|
faVsco.js – HubspotJournalPollingService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto...
|
[{"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":"#12121 on JY-20963-fix-import-on-deleted-entity, menu","depth":5,"on_screen":true,"help_text":"Pull request #12121 exists for current branch JY-20963-fix-import-on-deleted-entity","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":"TextRelayServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","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":"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":"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":"AXStaticText","text":"12","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"9","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\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","role_description":"text entry area","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}]...
|
8618909521242162662
|
-30141114868883220
|
typing_pause
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto...
|
85319
|
NULL
|
NULL
|
NULL
|
|
85320
|
2921
|
40
|
2026-05-28T12:18:07.140587+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970687140_m2.jpg...
|
PhpStorm
|
faVsco.js – HubspotJournalPollingService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error...
|
[{"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":"#12121 on JY-20963-fix-import-on-deleted-entity, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.11569149,"height":0.025538707},"on_screen":true,"help_text":"Pull request #12121 exists for current branch JY-20963-fix-import-on-deleted-entity","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.8374335,"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":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"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 'TextRelayServiceTest'","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 'TextRelayServiceTest'","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":"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":"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":"AXStaticText","text":"12","depth":4,"bounds":{"left":0.37865692,"top":0.15003991,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"9","depth":4,"bounds":{"left":0.39029256,"top":0.15003991,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39993352,"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.40724733,"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}]...
|
6731292379551279285
|
-7875264099512014368
|
typing_pause
|
hybrid
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
rapstomEV faVsco,ls ~ProjectvViewCooc#12121 on JY-20963-fix-InWindowwnapers.oh© VoiceConsentPrefix.phpe Not iicationsnObservers© AccountObserver.php© ActivityObserver.php© ContactObserver.php© [EMAIL]© ProfileObserver.phpSocialAccountObserver.phpUserObserver.php© UserRoleObserver.php› Co PoliciesC ProvidersQueuea ReoositonieRutesvserviceActivityAireoorsEJ AvatarlealendarOLeAkоkyscrco.on(©) Hubspot.JournalPollinaService.ohp x$512Cass nuosoo vourhalroto nobeiv* Acquire exclusive polling lock to prevent aultiple instancesrivate function acquirePollingLock(): boolBullhorne CopperimermobiacteDecorateActivityDummy› @ Helpersv im HibsodAccountSyncStrategyIhctiontContactSyncStrategy› DODTO331333› E Fieldswhtaurnd© HubspotClientCredenti@HubspotDealWebhook 340© HubspotJournalApiCie341© HubspotJournalPolling: 342© HubspotWebhookSubs© JournalApiResult.php© JournalßatchSizeLimitE© JournalEventTransforma Metadataa@poortunityswncstrateoy→lmlpae natioo* Rerease the potting lockrivate function releasePollingLock(): voidl...* Force release the polling lock (for emergency situations)ublic function forceReleaseLock(): voidf...* Signat the polling service to stop gracefullyublic function requestStop(): void{...}M canicetraiteU TextRelayServiceTest~©ActivityController.php© Kernel.phpSF jiminny@localhost)•• Thu 28 May 15:18:06+0.A console (PROD] x © Service.phpds consoeleu.68fminry~045 A1 441 X 66 ACONCAT(U.10, CASE WHEN U.id = t.ouner_id THEN • (ouner) ' ELSE "* END) AS user_id,Cacaado CodoxoKick off a new project. Make changes738select x fron text_relays where created at > +2826-85-81*:select * fron activities where user_id IN (7160, 18608) and created_at > '2026-05-22' order by id desc;id IN (18688, 13934, 7160):_id = 7160 order by id desc Linit 10;select x fron activity_searches where user_id = 31054;activity_search_filters where activity_search_id IN (88882, 88902);c. Fx TexiRelay service Teste© Multi-Picklist Layout Displayis this @HubspotJournalPollingService.php#L319 ok,l")Hode swioth56mtMedeetmeooltttlcharelwhires*4 space...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85319
|
2920
|
38
|
2026-05-28T12:18:06.806990+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970686806_m1.jpg...
|
PhpStorm
|
faVsco.js – HubspotJournalPollingService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
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
45
1
41
66
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993
SELECT * FROM users WHERE id = 25061;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 994;
SELECT * FROM crm_profiles WHERE user_id = 25061;
select * from crm_configurations where id = 834;
SELECT * FROM teams WHERE id = 882;
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 = 882 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;
SELECT * FROM contacts where crm_configuration_id = 834;
SELECT * FROM opportunities WHERE team_id = 933
# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');
AND id IN (8482561,18352941,19042734,19232139,19445140,19472541);
SELECT * FROM opportunity_contacts
WHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 485; #
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
select crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id
where crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')
# and l.converted_at IS NOT NULL
;
# [PASSWORD_DOTS]
SELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')
and opportunity_id IS NULL
order by id desc;
SELECT * FROM teams WHERE id = 604; # 598
SELECT * FROM activities WHERE id = 74410828; # [EMAIL]
SELECT * FROM accounts WHERE id = 20068382;
SELECT * FROM accounts WHERE id = 35186038;
SELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;
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 = 559 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;
select * from sidekick_settings where team_id = 781;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100
SELECT * FROM crm_layouts WHERE crm_configuration_id = 711;
SELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL
and is_internal = 0 and status = 'completed'
order by id desc;
SELECT * FROM crm_layout_entities
WHERE crm_layout_id IN (2352, 2353);
;
SELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;
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 = 556 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;
SELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;
select * from contacts
where crm_configuration_id = 530
and crm_provider_id = 872252;
select * from activities where crm_configuration_id = 530
and user_id = 14343 and type like '%softphone%'
and created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);
SELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t
JOIN crm_configurations c ON t.id = c.team_id
WHERE t.status = 'active';
SELECT * FROM teams where id = 1091;
SELECT * FROM crm_configurations where team_id = 1091;
SELECT * FROM activity_providers where team_id = 1091;
SELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT * FROM teams WHERE name LIKE '%Leadventure%';
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 = 1091 and sa.provider = 'salesforce';
SELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812
SELECT * FROM teams where id = 862;
SELECT * FROM crm_configurations where team_id = 862;
SELECT * FROM activity_providers where team_id = 862;
SELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT t.id, crm.id, crm.provider, ap.* FROM teams t
join crm_configurations crm on t.id = crm.team_id
join activity_providers ap on t.id = ap.team_id
where t.status = 'active' and ap.is_enabled = 1
and crm.provider = 'hubspot'
and ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',
'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');
SELECT * FROM teams where id = 1068;
SELECT * FROM crm_configurations where team_id = 1068;
SELECT * FROM activity_providers where team_id = 1068;
SELECT * FROM activities a
where crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')
and a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'
)
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by a.id desc;
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 = 1068 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262
SELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;
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 = 882 and sa.provider = 'hubspot';
select * from crm_layouts where crm_configuration_id = 834;
select * from crm_layout_entities where crm_layout_id = 2780;
select * from crm_fields where id IN (321153,321192,321193,321194);
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
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 = 1057 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8
SELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20
SELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10
SELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #
SELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;
select * from users where team_id = 51; # 7783
SELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130
select * from activity_searches where user_id = 7783;
select * from activity_search_filters where activity_search_id IN (32291, 32292);
SELECT asf.activity_search_id, asf.id, asf.value
FROM activity_search_filters asf
WHERE asf.filter = 'group_id'
AND asf.value IN (
SELECT CONCAT(
HEX(SUBSTR(uuid, 5, 4)), '-',
HEX(SUBSTR(uuid, 3, 2)), '-',
HEX(SUBSTR(uuid, 1, 2)), '-',
HEX(SUBSTR(uuid, 9, 2)), '-',
HEX(SUBSTR(uuid, 11))
)
FROM groups
WHERE deleted_at IS NOT NULL
);
SELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where provider = 'hubspot';
SELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133
SELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null
# [PASSWORD_DOTS]
select * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';
select
cp.*
# DISTINCT t.id
# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields
FROM crm_profiles cp
JOIN crm_configurations crm on crm.id = cp.crm_configuration_id
JOIN users u on u.id = cp.user_id
JOIN teams t ON t.id = crm.team_id
WHERE crm.provider = 'salesforce' and t.status = 'active'
and cp.archived_at IS NULL and u.deleted_at IS NULL
and t.id NOT IN (1093)
and t.id = 2
and cp.contact_fields IS NULL;
# and c.crm_provider_id = '003Uu00000ojD4NIAU';
SELECT * FROM users WHERE id = 26484;
SELECT * FROM crm_profiles WHERE user_id = 26484;
SELECT * FROM social_accounts WHERE sociable_id = 26484;
SELECT * FROM crm_configurations where provider = 'salesforce';
select * from users where id IN (10022, 10403);
select * from users where team_id IN (526);
select * from teams where id IN (526, 532);
select * from crm_configurations where id IN (500, 516);
select * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);
select * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';
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 = 526 and sa.provider = 'salesforce';
select * from team_settings where team_id IN (526, 532);
select * from users where id IN (22824);
select * from crm_profiles where crm_configuration_id IN (1026);
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 = 1093 and sa.provider = 'salesforce';
select * from teams where id = 1099;
select * from users where id = 29643
select * from activity_processing_states;
SELECT * FROM teams where name LIKE '%Fare%'; # 233
SELECT * FROM opportunities where crm_configuration_id = 215
# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'
;
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 = 1088 and sa.provider = 'hubspot';
SELECT * FROM teams order by updated_at DESC
SELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account
select * from crm_configurations where provider = 'pipedrive';
select * from teams where id = 957;
select * from crm_configurations where id = 957;
SELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743
SELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;
select * from users where team_id = 1; # 26726 - Gabriela Dureva
SELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific
select * from activities where user_id = 26726 order by id desc;
select * from contacts where crm_configuration_id = 1
and email IN ('[EMAIL]', '[EMAIL]'); # 2094416, 2093620
SELECT * FROM contacts WHERE id = 6284931;
SELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id
WHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;
select * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);
select * from crm_configurations where id = 1;
43801692-1aeb-32ce-acba-5b80a479701a
44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b
405975c0-b3d0-7aaa-821f-09d59cae6dd1
4caf848d-4bed-2299-b248-7788d41f9fca
49bedc3f-f196-eef3-89c3-dea6a3b4aa63
43420989-a09d-b8f8-9806-c8bbf7a02aac
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 = 1 and sa.provider = 'salesforce';
SELECT * FROM activities WHERE id = 75461988;
SELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;
select * from contacts where id = 17900517;
select * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id
where crm.provider != 'salesforce';
select * from users where id = 21047;
SELECT * FROM crm_configurations WHERE id = 892;
SELECT * FROM teams WHERE id = 942;
select * from opportunities where team_id = 942 order by updated_at desc;
select * from contacts where team_id = 942 order by updated_at desc;
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 = 942 and sa.provider = 'hubspot';
SELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430
SELECT * FROM crm_configurations WHERE id = 1;
SELECT * FROM teams WHERE crm_id = 1;
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 = 1 and sa.provider = 'salesforce';
select id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1
SELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430
select * from teams where id = 852;
select * from groups where id = 2286;
select * from sidekick_settings where team_id = 852;
select * from default_activity_types where team_id = 852;
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1 AND u.deleted_at IS NULL
AND u.crm_required = 1
AND u.team_id = 1
ORDER BY u.team_id;
SELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (
18481
);
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1
AND u.deleted_at IS NULL
AND u.crm_required = 1
# AND u.team_id = 1
AND p.id IS NULL -- Move this condition to WHERE clause
ORDER BY u.team_id;
SELECT * FROM opportunities WHERE id = 20002609;
select * from teams where id = 1122; # Velatir, 29953 - [EMAIL]
select * from crm_configurations where id = 1060;
select * from crm_layouts where crm_configuration_id = 1060;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;
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 = 1122 and sa.provider = 'hubspot';
select * from opportunities where team_id = 1122 order by updated_at desc;
select * from crm_field_data where object_type = 'contact';
SELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262
SELECT
CONCAT(u.id, CASE WHEN u.id = t.own...
|
[{"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":"#12121 on JY-20963-fix-import-on-deleted-entity, menu","depth":5,"on_screen":true,"help_text":"Pull request #12121 exists for current branch JY-20963-fix-import-on-deleted-entity","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":"TextRelayServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","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":"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":"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":"AXStaticText","text":"12","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"9","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\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","role_description":"text entry area","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":"45","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"41","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"66","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":"SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993\nSELECT * FROM users WHERE id = 25061;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 994;\nSELECT * FROM crm_profiles WHERE user_id = 25061;\n\nselect * from crm_configurations where id = 834;\nSELECT * FROM teams WHERE id = 882;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;\n\nSELECT * FROM contacts where crm_configuration_id = 834;\nSELECT * FROM opportunities WHERE team_id = 933\n# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');\nAND id IN (8482561,18352941,19042734,19232139,19445140,19472541);\nSELECT * FROM opportunity_contacts\nWHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 485; #\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nselect crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id\nwhere crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')\n# and l.converted_at IS NOT NULL\n;\n\n# ********************************************************************\nSELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')\nand opportunity_id IS NULL\norder by id desc;\n\nSELECT * FROM teams WHERE id = 604; # 598\nSELECT * FROM activities WHERE id = 74410828; # chelseaw@allvoices.co\nSELECT * FROM accounts WHERE id = 20068382;\nSELECT * FROM accounts WHERE id = 35186038;\n\nSELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 559 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;\nselect * from sidekick_settings where team_id = 781;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100\n\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 711;\nSELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL\nand is_internal = 0 and status = 'completed'\norder by id desc;\n\nSELECT * FROM crm_layout_entities\nWHERE crm_layout_id IN (2352, 2353);\n;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 556 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;\nSELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;\nselect * from contacts\nwhere crm_configuration_id = 530\nand crm_provider_id = 872252;\n\nselect * from activities where crm_configuration_id = 530\nand user_id = 14343 and type like '%softphone%'\nand created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);\n\n\nSELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t\nJOIN crm_configurations c ON t.id = c.team_id\nWHERE t.status = 'active';\n\nSELECT * FROM teams where id = 1091;\nSELECT * FROM crm_configurations where team_id = 1091;\nSELECT * FROM activity_providers where team_id = 1091;\nSELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT * FROM teams WHERE name LIKE '%Leadventure%';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1091 and sa.provider = 'salesforce';\n\nSELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812\nSELECT * FROM teams where id = 862;\nSELECT * FROM crm_configurations where team_id = 862;\nSELECT * FROM activity_providers where team_id = 862;\nSELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT t.id, crm.id, crm.provider, ap.* FROM teams t\njoin crm_configurations crm on t.id = crm.team_id\njoin activity_providers ap on t.id = ap.team_id\nwhere t.status = 'active' and ap.is_enabled = 1\nand crm.provider = 'hubspot'\nand ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',\n 'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');\n\nSELECT * FROM teams where id = 1068;\nSELECT * FROM crm_configurations where team_id = 1068;\nSELECT * FROM activity_providers where team_id = 1068;\n\nSELECT * FROM activities a\nwhere crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')\nand a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'\n )\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by a.id desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1068 and sa.provider = 'hubspot';\n\n# ********************************************************************\n# ********************************************************************\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262\nSELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\nselect * from crm_layouts where crm_configuration_id = 834;\nselect * from crm_layout_entities where crm_layout_id = 2780;\nselect * from crm_fields where id IN (321153,321192,321193,321194);\n\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1057 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8\n\nSELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20\n\nSELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10\n\nSELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #\n\nSELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;\nselect * from users where team_id = 51; # 7783\nSELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130\nselect * from activity_searches where user_id = 7783;\nselect * from activity_search_filters where activity_search_id IN (32291, 32292);\n\nSELECT asf.activity_search_id, asf.id, asf.value\nFROM activity_search_filters asf\nWHERE asf.filter = 'group_id'\nAND asf.value IN (\n SELECT CONCAT(\n HEX(SUBSTR(uuid, 5, 4)), '-',\n HEX(SUBSTR(uuid, 3, 2)), '-',\n HEX(SUBSTR(uuid, 1, 2)), '-',\n HEX(SUBSTR(uuid, 9, 2)), '-',\n HEX(SUBSTR(uuid, 11))\n )\n FROM groups\n WHERE deleted_at IS NOT NULL\n);\n\nSELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th\n# ********************************************************************\nSELECT * FROM crm_configurations where provider = 'hubspot';\nSELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133\nSELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null\n# ********************************************************************\n\nselect * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';\nselect\n cp.*\n# DISTINCT t.id\n# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields\nFROM crm_profiles cp\nJOIN crm_configurations crm on crm.id = cp.crm_configuration_id\nJOIN users u on u.id = cp.user_id\nJOIN teams t ON t.id = crm.team_id\nWHERE crm.provider = 'salesforce' and t.status = 'active'\n and cp.archived_at IS NULL and u.deleted_at IS NULL\n and t.id NOT IN (1093)\n and t.id = 2\n and cp.contact_fields IS NULL;\n# and c.crm_provider_id = '003Uu00000ojD4NIAU';\n\nSELECT * FROM users WHERE id = 26484;\nSELECT * FROM crm_profiles WHERE user_id = 26484;\nSELECT * FROM social_accounts WHERE sociable_id = 26484;\nSELECT * FROM crm_configurations where provider = 'salesforce';\nselect * from users where id IN (10022, 10403);\nselect * from users where team_id IN (526);\nselect * from teams where id IN (526, 532);\nselect * from crm_configurations where id IN (500, 516);\nselect * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);\nselect * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 526 and sa.provider = 'salesforce';\nselect * from team_settings where team_id IN (526, 532);\n\nselect * from users where id IN (22824);\nselect * from crm_profiles where crm_configuration_id IN (1026);\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1093 and sa.provider = 'salesforce';\n\nselect * from teams where id = 1099;\nselect * from users where id = 29643\n\nselect * from activity_processing_states;\n\nSELECT * FROM teams where name LIKE '%Fare%'; # 233\nSELECT * FROM opportunities where crm_configuration_id = 215\n# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1088 and sa.provider = 'hubspot';\n\nSELECT * FROM teams order by updated_at DESC\nSELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account\n\nselect * from crm_configurations where provider = 'pipedrive';\n\nselect * from teams where id = 957;\nselect * from crm_configurations where id = 957;\n\nSELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743\nSELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;\n\nselect * from users where team_id = 1; # 26726 - Gabriela Dureva\nSELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific\nselect * from activities where user_id = 26726 order by id desc;\nselect * from contacts where crm_configuration_id = 1\nand email IN ('charlotte.ward@prolific.com', 'frankie.bryant@prolific.com'); # 2094416, 2093620\nSELECT * FROM contacts WHERE id = 6284931;\n\nSELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id\nWHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;\n\nselect * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);\nselect * from crm_configurations where id = 1;\n\n43801692-1aeb-32ce-acba-5b80a479701a\n44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b\n405975c0-b3d0-7aaa-821f-09d59cae6dd1\n4caf848d-4bed-2299-b248-7788d41f9fca\n49bedc3f-f196-eef3-89c3-dea6a3b4aa63\n43420989-a09d-b8f8-9806-c8bbf7a02aac\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nSELECT * FROM activities WHERE id = 75461988;\n\nSELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;\n\nselect * from contacts where id = 17900517;\n\nselect * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id\nwhere crm.provider != 'salesforce';\n\nselect * from users where id = 21047;\nSELECT * FROM crm_configurations WHERE id = 892;\nSELECT * FROM teams WHERE id = 942;\nselect * from opportunities where team_id = 942 order by updated_at desc;\nselect * from contacts where team_id = 942 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 942 and sa.provider = 'hubspot';\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430\nSELECT * FROM crm_configurations WHERE id = 1;\nSELECT * FROM teams WHERE crm_id = 1;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nselect id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1\nSELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430\n\nselect * from teams where id = 852;\nselect * from groups where id = 2286;\nselect * from sidekick_settings where team_id = 852;\nselect * from default_activity_types where team_id = 852;\n\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1 AND u.deleted_at IS NULL\nAND u.crm_required = 1\nAND u.team_id = 1\nORDER BY u.team_id;\n\nSELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (\n18481\n );\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1\n AND u.deleted_at IS NULL\n AND u.crm_required = 1\n# AND u.team_id = 1\n AND p.id IS NULL -- Move this condition to WHERE clause\nORDER BY u.team_id;\n\nSELECT * FROM opportunities WHERE id = 20002609;\nselect * from teams where id = 1122; # Velatir, 29953 - christian@velatir.com\nselect * from crm_configurations where id = 1060;\nselect * from crm_layouts where crm_configuration_id = 1060;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1122 and sa.provider = 'hubspot';\nselect * from opportunities where team_id = 1122 order by updated_at desc;\n\nselect * from crm_field_data where object_type = 'contact';\n\nSELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 248 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS\nSELECT * FROM users where id = 24115;\nSELECT * FROM accounts where id = 4002896;\nSELECT * FROM teams WHERE name LIKE '%adswerve%';\nSELECT * FROM opportunities where crm_configuration_id = 230 AND crm_provider_id IN (\"0069N000003GIQ9QAO\",\"0061r000019yGP9AAM\",\"0066900001S2KWlAAN\",\"0066900001TDpj2AAD\",\"0066900001b8uEwAAI\",\"0069N000001rQi0QAE\",\"006QF00000KD40mYAD\",\"006QF00000LzpRJYAZ\",\"0069N000002uomtQAA\",\"0069N000002xlMLQAY\",\"0066900001NV6ubAAD\",\"0061r00001HJp45AAD\",\"006QF00000uTlUoYAK\",\"006QF00000v0bZqYAI\");\nSELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203\n\nSELECT u.id, u.email, ac.name, a.* FROM activities a\nJOIN users u ON a.user_id = u.id\nJOIN accounts ac ON a.account_id = ac.id\nWHERE\nuuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or\nuuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or\nuuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;\n\nselect * from users where id = 5825;\nSELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;\n\nselect * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;\n19594, 862\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 862 and sa.provider = 'salesforce';\n\nselect * from automated_reports where id = 36;\nselect ar.frequency, r.*, ar.* from automated_report_results r\njoin automated_reports ar on r.report_id = ar.id\nwhere ar.frequency != 'one_off';\n\nselect s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;\nselect * from nudges n where n.activity_search_id\n\nselect * from teams where created_at > '2026-03-09';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;\n\nselect * from users where team_id = 1 and name like '%Lukas%'; # 7160\n\nSELECT * FROM teams WHERE id = 575;\nselect * from opportunities where team_id = 575;\nSELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,\nselect * from opportunities where team_id = 1126;\nSELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,\nselect * from opportunities where team_id = 1125;\nselect * from contacts c\nwhere c.team_id = 882;\n\nSELECT * FROM activities WHERE id = 76822967;\nSELECT * FROM crm_profiles WHERE user_id = 15440;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 555;\nSELECT * FROM crm_configurations WHERE id = 555;\nSELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 581 and sa.provider = 'salesforce';\n\nSELECT * FROM automated_report_results order by id desc;\n\nselect * from features;\nselect * from team_features where feature_id = 40;\n\nselect * from teams where id = 556;\n\nselect * from automated_reports;\nwhere id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , [\"pdf\",\"podcast\"]\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\nselect * from automated_report_results order by id desc;\nSELECT * FROM automated_report_results WHERE id = 1919;\n\nselect * from automated_report_results WHERE report_id = 54;\n\nselect * from opportunities where id = 7594349;\n\nSELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067 - jiminnyintegration@lesmills.com\nselect * from playbooks where team_id = 711; # event 226147\nSELECT * FROM playbook_categories WHERE playbook_id = 5515;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event';\nSELECT * FROM crm_fields WHERE id = 226147;\nSELECT * FROM crm_field_values WHERE crm_field_id = 226147;\n\nSELECT * FROM crm_configurations WHERE id = 692;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 711 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles cp JOIN users u on u.id = cp.user_id WHERE u.team_id = 711;\n\nselect * from leads;\n\nselect * from calendars;\n\nSELECT\n t.id AS team_id,\n t.name,\n LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1)) AS calendar_domain\nFROM teams t\nJOIN users u ON u.team_id = t.id\nJOIN calendars c ON c.user_id = u.id AND c.status = 'active' AND c.calendar_provider_id LIKE '%@%'\nLEFT JOIN team_domains td\n ON td.team_id = t.id\n AND td.deleted_at IS NULL\n AND td.domain = LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1))\nGROUP BY t.id, t.name, calendar_domain\nORDER BY t.name, calendar_domain;\n\nselect * from users u join calendars c on c.user_id = u.id\nwhere u.team_id = 882;\n\n\nselect * from activities where id = 74049485; # team 563 crm 537\nselect * from activities where id = 73272382; # team 563 crm 537\nselect * from activities where id = 64400389; # team 563 crm 537\nselect * from activities where id = 58081273; # team 563 crm 537\nselect * from activities where id = 54520297; # team 563 crm 537\nselect * from participants where activity_id = 58081273;\n\nselect * from activities where crm_configuration_id = 537 and provider = 'aircall'\nand account_id = 19003658 order by updated_at desc;\n\nselect * from contacts where crm_configuration_id = 537 and id = 35957759;\nselect * from accounts where crm_configuration_id = 537 and id = 19003658;\n\nselect * from automated_report_results where id = 1976;\nselect * from automated_reports where id = 583;\nselect * from activity_searches where id = 87714;\nselect * from activity_search_filters where activity_search_id = 87714;\n\nSELECT * FROM activities WHERE uuid_to_bin('8827f672-202d-4162-9d04-73ff5f0566a9') = uuid\nor uuid_to_bin('47842446-af51-4bcb-854f-cc6560290101') = uuid;\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot';\nselect * from rate_limits;\n\nselect * from automated_report_results where media_type = 'pdf' and status = 2\nand id IN (18, 1872);\nselect * from automated_reports where id = 54;\nSELECT * FROM users WHERE id IN (24623,29443,29613);\n\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\n\nselect * from text_relays where created_at > '2026-05-01';\nand id IN (32415, 32416);\n# and id = 32412;\n\nselect * from users where team_id = 2 and email like '%scott%' and id = 29510;\n\nSELECT * FROM activities WHERE uuid_to_bin('67cebfc2-ed56-44a2-8c68-7a0286ed8618') = uuid; # 79763436\n\nSELECT email_provider_id, COUNT(*) as count, GROUP_CONCAT(id) as ids, GROUP_CONCAT(status) as statuses\nFROM text_relays\nWHERE email_provider_id IN ('19e2027868a64b42', '19e2033ed8ea6b10')\nGROUP BY email_provider_id;\nSELECT id, status, telephony_provider_id, created_at\nFROM activities\nWHERE id IN (80028719, 80028846);\nSELECT id, status, code, email_sent_at, created_at, updated_at\nFROM text_relays\nWHERE id IN (32415, 32416);\nSELECT id, status, code, sender, recipient, created_at\nFROM text_relays\nWHERE sender LIKE '%mario.georgiev%' OR sender LIKE '%stoyan.tomov%'\nORDER BY created_at DESC\nLIMIT 10;\n\nSELECT id, uuid, status, code, sender, recipient, created_at, updated_at\nFROM text_relays\nWHERE uuid = uuid_to_bin('0626141c-27a6-4d8c-aff8-7c8020a2c656');\n\n# ***************\nSELECT DISTINCT u.id, u.email, u.name, u.softphone_number, COUNT(a.id) as sms_count\nFROM users u\nINNER JOIN activities a ON u.id = .user_id\nWHERE a.type LIKE 'sms%'\nAND a.created_at > DATE_SUB(NOW(), INTERVAL 30 DAY)\nGROUP BY u.id, u.email, u.name, u.softphone_number\nORDER BY sms_count DESC;\n\nselect * from teams where id = 1;\n\nselect * from roles;\n\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1117 and sa.provider = 'hubspot';\nSELECT * FROM activities WHERE uuid_to_bin('8024fffb-2df7-4017-91f4-d9f896050248') = uuid; # 79933459 YES\nSELECT * FROM activities WHERE uuid_to_bin('25529043-8094-4781-927f-4f4da2a8185c') = uuid; # 80186192 NO\nSELECT * FROM crm_configurations WHERE id = 1053;\nSELECT * FROM teams WHERE id = 1117;\nselect * from users where id = 30249;\nselect * from playbooks where id = 5473;\nselect * from playbook_categories where id = 43783;\nselect * from playbook_categories where playbook_id = 5473;\nselect * from crm_fields where id = 659242;\nselect * from crm_field_values where crm_field_id = 659242;\n\nSELECT * FROM crm_field_data fd\n# JOIN crm_fields f ON fd.crm_field_id = f.id\n# JOIN activities a ON fd.activity_id = a.id\nWHERE activity_id = 79933459\n# AND f.crm_provider_id = 'hs_activity_type';\n\n\nSELECT * FROM activity_messages;\nselect * from text_relays where created_at > '2026-05-01';\nselect * from activities where user_id IN (7160, 18608) and created_at > '2026-05-22' order by id desc;\nselect * from users where team_id = 1 and id IN (18608, 13934, 7160);\nselect * from activities where user_id = 7160 order by id desc limit 10;\n\nselect * from accounts where team_id = 1 and name = 'Column5';\n\nselect * from users where name like '%Subra%'; # 31054, 1117\nselect * from teams where id = 1117;\nselect * from activity_searches where user_id = 31054;\nselect * from activity_search_filters where activity_search_id IN (88882, 88902);","depth":4,"on_screen":true,"value":"SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993\nSELECT * FROM users WHERE id = 25061;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 994;\nSELECT * FROM crm_profiles WHERE user_id = 25061;\n\nselect * from crm_configurations where id = 834;\nSELECT * FROM teams WHERE id = 882;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;\n\nSELECT * FROM contacts where crm_configuration_id = 834;\nSELECT * FROM opportunities WHERE team_id = 933\n# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');\nAND id IN (8482561,18352941,19042734,19232139,19445140,19472541);\nSELECT * FROM opportunity_contacts\nWHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 485; #\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nselect crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id\nwhere crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')\n# and l.converted_at IS NOT NULL\n;\n\n# ********************************************************************\nSELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')\nand opportunity_id IS NULL\norder by id desc;\n\nSELECT * FROM teams WHERE id = 604; # 598\nSELECT * FROM activities WHERE id = 74410828; # chelseaw@allvoices.co\nSELECT * FROM accounts WHERE id = 20068382;\nSELECT * FROM accounts WHERE id = 35186038;\n\nSELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 559 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;\nselect * from sidekick_settings where team_id = 781;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100\n\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 711;\nSELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL\nand is_internal = 0 and status = 'completed'\norder by id desc;\n\nSELECT * FROM crm_layout_entities\nWHERE crm_layout_id IN (2352, 2353);\n;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 556 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;\nSELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;\nselect * from contacts\nwhere crm_configuration_id = 530\nand crm_provider_id = 872252;\n\nselect * from activities where crm_configuration_id = 530\nand user_id = 14343 and type like '%softphone%'\nand created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);\n\n\nSELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t\nJOIN crm_configurations c ON t.id = c.team_id\nWHERE t.status = 'active';\n\nSELECT * FROM teams where id = 1091;\nSELECT * FROM crm_configurations where team_id = 1091;\nSELECT * FROM activity_providers where team_id = 1091;\nSELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT * FROM teams WHERE name LIKE '%Leadventure%';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1091 and sa.provider = 'salesforce';\n\nSELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812\nSELECT * FROM teams where id = 862;\nSELECT * FROM crm_configurations where team_id = 862;\nSELECT * FROM activity_providers where team_id = 862;\nSELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT t.id, crm.id, crm.provider, ap.* FROM teams t\njoin crm_configurations crm on t.id = crm.team_id\njoin activity_providers ap on t.id = ap.team_id\nwhere t.status = 'active' and ap.is_enabled = 1\nand crm.provider = 'hubspot'\nand ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',\n 'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');\n\nSELECT * FROM teams where id = 1068;\nSELECT * FROM crm_configurations where team_id = 1068;\nSELECT * FROM activity_providers where team_id = 1068;\n\nSELECT * FROM activities a\nwhere crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')\nand a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'\n )\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by a.id desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1068 and sa.provider = 'hubspot';\n\n# ********************************************************************\n# ********************************************************************\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262\nSELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\nselect * from crm_layouts where crm_configuration_id = 834;\nselect * from crm_layout_entities where crm_layout_id = 2780;\nselect * from crm_fields where id IN (321153,321192,321193,321194);\n\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1057 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8\n\nSELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20\n\nSELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10\n\nSELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #\n\nSELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;\nselect * from users where team_id = 51; # 7783\nSELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130\nselect * from activity_searches where user_id = 7783;\nselect * from activity_search_filters where activity_search_id IN (32291, 32292);\n\nSELECT asf.activity_search_id, asf.id, asf.value\nFROM activity_search_filters asf\nWHERE asf.filter = 'group_id'\nAND asf.value IN (\n SELECT CONCAT(\n HEX(SUBSTR(uuid, 5, 4)), '-',\n HEX(SUBSTR(uuid, 3, 2)), '-',\n HEX(SUBSTR(uuid, 1, 2)), '-',\n HEX(SUBSTR(uuid, 9, 2)), '-',\n HEX(SUBSTR(uuid, 11))\n )\n FROM groups\n WHERE deleted_at IS NOT NULL\n);\n\nSELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th\n# ********************************************************************\nSELECT * FROM crm_configurations where provider = 'hubspot';\nSELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133\nSELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null\n# ********************************************************************\n\nselect * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';\nselect\n cp.*\n# DISTINCT t.id\n# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields\nFROM crm_profiles cp\nJOIN crm_configurations crm on crm.id = cp.crm_configuration_id\nJOIN users u on u.id = cp.user_id\nJOIN teams t ON t.id = crm.team_id\nWHERE crm.provider = 'salesforce' and t.status = 'active'\n and cp.archived_at IS NULL and u.deleted_at IS NULL\n and t.id NOT IN (1093)\n and t.id = 2\n and cp.contact_fields IS NULL;\n# and c.crm_provider_id = '003Uu00000ojD4NIAU';\n\nSELECT * FROM users WHERE id = 26484;\nSELECT * FROM crm_profiles WHERE user_id = 26484;\nSELECT * FROM social_accounts WHERE sociable_id = 26484;\nSELECT * FROM crm_configurations where provider = 'salesforce';\nselect * from users where id IN (10022, 10403);\nselect * from users where team_id IN (526);\nselect * from teams where id IN (526, 532);\nselect * from crm_configurations where id IN (500, 516);\nselect * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);\nselect * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 526 and sa.provider = 'salesforce';\nselect * from team_settings where team_id IN (526, 532);\n\nselect * from users where id IN (22824);\nselect * from crm_profiles where crm_configuration_id IN (1026);\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1093 and sa.provider = 'salesforce';\n\nselect * from teams where id = 1099;\nselect * from users where id = 29643\n\nselect * from activity_processing_states;\n\nSELECT * FROM teams where name LIKE '%Fare%'; # 233\nSELECT * FROM opportunities where crm_configuration_id = 215\n# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1088 and sa.provider = 'hubspot';\n\nSELECT * FROM teams order by updated_at DESC\nSELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account\n\nselect * from crm_configurations where provider = 'pipedrive';\n\nselect * from teams where id = 957;\nselect * from crm_configurations where id = 957;\n\nSELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743\nSELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;\n\nselect * from users where team_id = 1; # 26726 - Gabriela Dureva\nSELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific\nselect * from activities where user_id = 26726 order by id desc;\nselect * from contacts where crm_configuration_id = 1\nand email IN ('charlotte.ward@prolific.com', 'frankie.bryant@prolific.com'); # 2094416, 2093620\nSELECT * FROM contacts WHERE id = 6284931;\n\nSELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id\nWHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;\n\nselect * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);\nselect * from crm_configurations where id = 1;\n\n43801692-1aeb-32ce-acba-5b80a479701a\n44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b\n405975c0-b3d0-7aaa-821f-09d59cae6dd1\n4caf848d-4bed-2299-b248-7788d41f9fca\n49bedc3f-f196-eef3-89c3-dea6a3b4aa63\n43420989-a09d-b8f8-9806-c8bbf7a02aac\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nSELECT * FROM activities WHERE id = 75461988;\n\nSELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;\n\nselect * from contacts where id = 17900517;\n\nselect * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id\nwhere crm.provider != 'salesforce';\n\nselect * from users where id = 21047;\nSELECT * FROM crm_configurations WHERE id = 892;\nSELECT * FROM teams WHERE id = 942;\nselect * from opportunities where team_id = 942 order by updated_at desc;\nselect * from contacts where team_id = 942 order by updated_at desc;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 942 and sa.provider = 'hubspot';\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430\nSELECT * FROM crm_configurations WHERE id = 1;\nSELECT * FROM teams WHERE crm_id = 1;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nselect id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1\nSELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430\n\nselect * from teams where id = 852;\nselect * from groups where id = 2286;\nselect * from sidekick_settings where team_id = 852;\nselect * from default_activity_types where team_id = 852;\n\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1 AND u.deleted_at IS NULL\nAND u.crm_required = 1\nAND u.team_id = 1\nORDER BY u.team_id;\n\nSELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (\n18481\n );\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1\n AND u.deleted_at IS NULL\n AND u.crm_required = 1\n# AND u.team_id = 1\n AND p.id IS NULL -- Move this condition to WHERE clause\nORDER BY u.team_id;\n\nSELECT * FROM opportunities WHERE id = 20002609;\nselect * from teams where id = 1122; # Velatir, 29953 - christian@velatir.com\nselect * from crm_configurations where id = 1060;\nselect * from crm_layouts where crm_configuration_id = 1060;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1122 and sa.provider = 'hubspot';\nselect * from opportunities where team_id = 1122 order by updated_at desc;\n\nselect * from crm_field_data where object_type = 'contact';\n\nSELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 248 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS\nSELECT * FROM users where id = 24115;\nSELECT * FROM accounts where id = 4002896;\nSELECT * FROM teams WHERE name LIKE '%adswerve%';\nSELECT * FROM opportunities where crm_configuration_id = 230 AND crm_provider_id IN (\"0069N000003GIQ9QAO\",\"0061r000019yGP9AAM\",\"0066900001S2KWlAAN\",\"0066900001TDpj2AAD\",\"0066900001b8uEwAAI\",\"0069N000001rQi0QAE\",\"006QF00000KD40mYAD\",\"006QF00000LzpRJYAZ\",\"0069N000002uomtQAA\",\"0069N000002xlMLQAY\",\"0066900001NV6ubAAD\",\"0061r00001HJp45AAD\",\"006QF00000uTlUoYAK\",\"006QF00000v0bZqYAI\");\nSELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203\n\nSELECT u.id, u.email, ac.name, a.* FROM activities a\nJOIN users u ON a.user_id = u.id\nJOIN accounts ac ON a.account_id = ac.id\nWHERE\nuuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or\nuuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or\nuuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;\n\nselect * from users where id = 5825;\nSELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;\n\nselect * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;\n19594, 862\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 862 and sa.provider = 'salesforce';\n\nselect * from automated_reports where id = 36;\nselect ar.frequency, r.*, ar.* from automated_report_results r\njoin automated_reports ar on r.report_id = ar.id\nwhere ar.frequency != 'one_off';\n\nselect s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;\nselect * from nudges n where n.activity_search_id\n\nselect * from teams where created_at > '2026-03-09';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;\n\nselect * from users where team_id = 1 and name like '%Lukas%'; # 7160\n\nSELECT * FROM teams WHERE id = 575;\nselect * from opportunities where team_id = 575;\nSELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,\nselect * from opportunities where team_id = 1126;\nSELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,\nselect * from opportunities where team_id = 1125;\nselect * from contacts c\nwhere c.team_id = 882;\n\nSELECT * FROM activities WHERE id = 76822967;\nSELECT * FROM crm_profiles WHERE user_id = 15440;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 555;\nSELECT * FROM crm_configurations WHERE id = 555;\nSELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 581 and sa.provider = 'salesforce';\n\nSELECT * FROM automated_report_results order by id desc;\n\nselect * from features;\nselect * from team_features where feature_id = 40;\n\nselect * from teams where id = 556;\n\nselect * from automated_reports;\nwhere id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , [\"pdf\",\"podcast\"]\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\nselect * from automated_report_results order by id desc;\nSELECT * FROM automated_report_results WHERE id = 1919;\n\nselect * from automated_report_results WHERE report_id = 54;\n\nselect * from opportunities where id = 7594349;\n\nSELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067 - jiminnyintegration@lesmills.com\nselect * from playbooks where team_id = 711; # event 226147\nSELECT * FROM playbook_categories WHERE playbook_id = 5515;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event';\nSELECT * FROM crm_fields WHERE id = 226147;\nSELECT * FROM crm_field_values WHERE crm_field_id = 226147;\n\nSELECT * FROM crm_configurations WHERE id = 692;\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 711 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles cp JOIN users u on u.id = cp.user_id WHERE u.team_id = 711;\n\nselect * from leads;\n\nselect * from calendars;\n\nSELECT\n t.id AS team_id,\n t.name,\n LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1)) AS calendar_domain\nFROM teams t\nJOIN users u ON u.team_id = t.id\nJOIN calendars c ON c.user_id = u.id AND c.status = 'active' AND c.calendar_provider_id LIKE '%@%'\nLEFT JOIN team_domains td\n ON td.team_id = t.id\n AND td.deleted_at IS NULL\n AND td.domain = LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1))\nGROUP BY t.id, t.name, calendar_domain\nORDER BY t.name, calendar_domain;\n\nselect * from users u join calendars c on c.user_id = u.id\nwhere u.team_id = 882;\n\n\nselect * from activities where id = 74049485; # team 563 crm 537\nselect * from activities where id = 73272382; # team 563 crm 537\nselect * from activities where id = 64400389; # team 563 crm 537\nselect * from activities where id = 58081273; # team 563 crm 537\nselect * from activities where id = 54520297; # team 563 crm 537\nselect * from participants where activity_id = 58081273;\n\nselect * from activities where crm_configuration_id = 537 and provider = 'aircall'\nand account_id = 19003658 order by updated_at desc;\n\nselect * from contacts where crm_configuration_id = 537 and id = 35957759;\nselect * from accounts where crm_configuration_id = 537 and id = 19003658;\n\nselect * from automated_report_results where id = 1976;\nselect * from automated_reports where id = 583;\nselect * from activity_searches where id = 87714;\nselect * from activity_search_filters where activity_search_id = 87714;\n\nSELECT * FROM activities WHERE uuid_to_bin('8827f672-202d-4162-9d04-73ff5f0566a9') = uuid\nor uuid_to_bin('47842446-af51-4bcb-854f-cc6560290101') = uuid;\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot';\nselect * from rate_limits;\n\nselect * from automated_report_results where media_type = 'pdf' and status = 2\nand id IN (18, 1872);\nselect * from automated_reports where id = 54;\nSELECT * FROM users WHERE id IN (24623,29443,29613);\n\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\n\nselect * from text_relays where created_at > '2026-05-01';\nand id IN (32415, 32416);\n# and id = 32412;\n\nselect * from users where team_id = 2 and email like '%scott%' and id = 29510;\n\nSELECT * FROM activities WHERE uuid_to_bin('67cebfc2-ed56-44a2-8c68-7a0286ed8618') = uuid; # 79763436\n\nSELECT email_provider_id, COUNT(*) as count, GROUP_CONCAT(id) as ids, GROUP_CONCAT(status) as statuses\nFROM text_relays\nWHERE email_provider_id IN ('19e2027868a64b42', '19e2033ed8ea6b10')\nGROUP BY email_provider_id;\nSELECT id, status, telephony_provider_id, created_at\nFROM activities\nWHERE id IN (80028719, 80028846);\nSELECT id, status, code, email_sent_at, created_at, updated_at\nFROM text_relays\nWHERE id IN (32415, 32416);\nSELECT id, status, code, sender, recipient, created_at\nFROM text_relays\nWHERE sender LIKE '%mario.georgiev%' OR sender LIKE '%stoyan.tomov%'\nORDER BY created_at DESC\nLIMIT 10;\n\nSELECT id, uuid, status, code, sender, recipient, created_at, updated_at\nFROM text_relays\nWHERE uuid = uuid_to_bin('0626141c-27a6-4d8c-aff8-7c8020a2c656');\n\n# ***************\nSELECT DISTINCT u.id, u.email, u.name, u.softphone_number, COUNT(a.id) as sms_count\nFROM users u\nINNER JOIN activities a ON u.id = .user_id\nWHERE a.type LIKE 'sms%'\nAND a.created_at > DATE_SUB(NOW(), INTERVAL 30 DAY)\nGROUP BY u.id, u.email, u.name, u.softphone_number\nORDER BY sms_count DESC;\n\nselect * from teams where id = 1;\n\nselect * from roles;\n\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 on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1117 and sa.provider = 'hubspot';\nSELECT * FROM activities WHERE uuid_to_bin('8024fffb-2df7-4017-91f4-d9f896050248') = uuid; # 79933459 YES\nSELECT * FROM activities WHERE uuid_to_bin('25529043-8094-4781-927f-4f4da2a8185c') = uuid; # 80186192 NO\nSELECT * FROM crm_configurations WHERE id = 1053;\nSELECT * FROM teams WHERE id = 1117;\nselect * from users where id = 30249;\nselect * from playbooks where id = 5473;\nselect * from playbook_categories where id = 43783;\nselect * from playbook_categories where playbook_id = 5473;\nselect * from crm_fields where id = 659242;\nselect * from crm_field_values where crm_field_id = 659242;\n\nSELECT * FROM crm_field_data fd\n# JOIN crm_fields f ON fd.crm_field_id = f.id\n# JOIN activities a ON fd.activity_id = a.id\nWHERE activity_id = 79933459\n# AND f.crm_provider_id = 'hs_activity_type';\n\n\nSELECT * FROM activity_messages;\nselect * from text_relays where created_at > '2026-05-01';\nselect * from activities where user_id IN (7160, 18608) and created_at > '2026-05-22' order by id desc;\nselect * from users where team_id = 1 and id IN (18608, 13934, 7160);\nselect * from activities where user_id = 7160 order by id desc limit 10;\n\nselect * from accounts where team_id = 1 and name = 'Column5';\n\nselect * from users where name like '%Subra%'; # 31054, 1117\nselect * from teams where id = 1117;\nselect * from activity_searches where user_id = 31054;\nselect * from activity_search_filters where activity_search_id IN (88882, 88902);","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":"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}]...
|
2526630547225287665
|
1137771414947247693
|
typing_pause
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
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
45
1
41
66
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993
SELECT * FROM users WHERE id = 25061;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 994;
SELECT * FROM crm_profiles WHERE user_id = 25061;
select * from crm_configurations where id = 834;
SELECT * FROM teams WHERE id = 882;
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 = 882 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;
SELECT * FROM contacts where crm_configuration_id = 834;
SELECT * FROM opportunities WHERE team_id = 933
# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');
AND id IN (8482561,18352941,19042734,19232139,19445140,19472541);
SELECT * FROM opportunity_contacts
WHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 485; #
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
select crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id
where crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')
# and l.converted_at IS NOT NULL
;
# [PASSWORD_DOTS]
SELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')
and opportunity_id IS NULL
order by id desc;
SELECT * FROM teams WHERE id = 604; # 598
SELECT * FROM activities WHERE id = 74410828; # [EMAIL]
SELECT * FROM accounts WHERE id = 20068382;
SELECT * FROM accounts WHERE id = 35186038;
SELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;
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 = 559 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;
select * from sidekick_settings where team_id = 781;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100
SELECT * FROM crm_layouts WHERE crm_configuration_id = 711;
SELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL
and is_internal = 0 and status = 'completed'
order by id desc;
SELECT * FROM crm_layout_entities
WHERE crm_layout_id IN (2352, 2353);
;
SELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;
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 = 556 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;
SELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;
select * from contacts
where crm_configuration_id = 530
and crm_provider_id = 872252;
select * from activities where crm_configuration_id = 530
and user_id = 14343 and type like '%softphone%'
and created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);
SELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t
JOIN crm_configurations c ON t.id = c.team_id
WHERE t.status = 'active';
SELECT * FROM teams where id = 1091;
SELECT * FROM crm_configurations where team_id = 1091;
SELECT * FROM activity_providers where team_id = 1091;
SELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT * FROM teams WHERE name LIKE '%Leadventure%';
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 = 1091 and sa.provider = 'salesforce';
SELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812
SELECT * FROM teams where id = 862;
SELECT * FROM crm_configurations where team_id = 862;
SELECT * FROM activity_providers where team_id = 862;
SELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT t.id, crm.id, crm.provider, ap.* FROM teams t
join crm_configurations crm on t.id = crm.team_id
join activity_providers ap on t.id = ap.team_id
where t.status = 'active' and ap.is_enabled = 1
and crm.provider = 'hubspot'
and ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',
'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');
SELECT * FROM teams where id = 1068;
SELECT * FROM crm_configurations where team_id = 1068;
SELECT * FROM activity_providers where team_id = 1068;
SELECT * FROM activities a
where crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')
and a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'
)
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by a.id desc;
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 = 1068 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;
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 = 933 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262
SELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;
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 = 882 and sa.provider = 'hubspot';
select * from crm_layouts where crm_configuration_id = 834;
select * from crm_layout_entities where crm_layout_id = 2780;
select * from crm_fields where id IN (321153,321192,321193,321194);
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
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 = 1057 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8
SELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20
SELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10
SELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #
SELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;
select * from users where team_id = 51; # 7783
SELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130
select * from activity_searches where user_id = 7783;
select * from activity_search_filters where activity_search_id IN (32291, 32292);
SELECT asf.activity_search_id, asf.id, asf.value
FROM activity_search_filters asf
WHERE asf.filter = 'group_id'
AND asf.value IN (
SELECT CONCAT(
HEX(SUBSTR(uuid, 5, 4)), '-',
HEX(SUBSTR(uuid, 3, 2)), '-',
HEX(SUBSTR(uuid, 1, 2)), '-',
HEX(SUBSTR(uuid, 9, 2)), '-',
HEX(SUBSTR(uuid, 11))
)
FROM groups
WHERE deleted_at IS NOT NULL
);
SELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where provider = 'hubspot';
SELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133
SELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null
# [PASSWORD_DOTS]
select * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';
select
cp.*
# DISTINCT t.id
# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields
FROM crm_profiles cp
JOIN crm_configurations crm on crm.id = cp.crm_configuration_id
JOIN users u on u.id = cp.user_id
JOIN teams t ON t.id = crm.team_id
WHERE crm.provider = 'salesforce' and t.status = 'active'
and cp.archived_at IS NULL and u.deleted_at IS NULL
and t.id NOT IN (1093)
and t.id = 2
and cp.contact_fields IS NULL;
# and c.crm_provider_id = '003Uu00000ojD4NIAU';
SELECT * FROM users WHERE id = 26484;
SELECT * FROM crm_profiles WHERE user_id = 26484;
SELECT * FROM social_accounts WHERE sociable_id = 26484;
SELECT * FROM crm_configurations where provider = 'salesforce';
select * from users where id IN (10022, 10403);
select * from users where team_id IN (526);
select * from teams where id IN (526, 532);
select * from crm_configurations where id IN (500, 516);
select * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);
select * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';
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 = 526 and sa.provider = 'salesforce';
select * from team_settings where team_id IN (526, 532);
select * from users where id IN (22824);
select * from crm_profiles where crm_configuration_id IN (1026);
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 = 1093 and sa.provider = 'salesforce';
select * from teams where id = 1099;
select * from users where id = 29643
select * from activity_processing_states;
SELECT * FROM teams where name LIKE '%Fare%'; # 233
SELECT * FROM opportunities where crm_configuration_id = 215
# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'
;
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 = 1088 and sa.provider = 'hubspot';
SELECT * FROM teams order by updated_at DESC
SELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account
select * from crm_configurations where provider = 'pipedrive';
select * from teams where id = 957;
select * from crm_configurations where id = 957;
SELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743
SELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;
select * from users where team_id = 1; # 26726 - Gabriela Dureva
SELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific
select * from activities where user_id = 26726 order by id desc;
select * from contacts where crm_configuration_id = 1
and email IN ('[EMAIL]', '[EMAIL]'); # 2094416, 2093620
SELECT * FROM contacts WHERE id = 6284931;
SELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id
WHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;
select * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);
select * from crm_configurations where id = 1;
43801692-1aeb-32ce-acba-5b80a479701a
44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b
405975c0-b3d0-7aaa-821f-09d59cae6dd1
4caf848d-4bed-2299-b248-7788d41f9fca
49bedc3f-f196-eef3-89c3-dea6a3b4aa63
43420989-a09d-b8f8-9806-c8bbf7a02aac
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 = 1 and sa.provider = 'salesforce';
SELECT * FROM activities WHERE id = 75461988;
SELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;
select * from contacts where id = 17900517;
select * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id
where crm.provider != 'salesforce';
select * from users where id = 21047;
SELECT * FROM crm_configurations WHERE id = 892;
SELECT * FROM teams WHERE id = 942;
select * from opportunities where team_id = 942 order by updated_at desc;
select * from contacts where team_id = 942 order by updated_at desc;
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 = 942 and sa.provider = 'hubspot';
SELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430
SELECT * FROM crm_configurations WHERE id = 1;
SELECT * FROM teams WHERE crm_id = 1;
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 = 1 and sa.provider = 'salesforce';
select id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1
SELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430
select * from teams where id = 852;
select * from groups where id = 2286;
select * from sidekick_settings where team_id = 852;
select * from default_activity_types where team_id = 852;
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1 AND u.deleted_at IS NULL
AND u.crm_required = 1
AND u.team_id = 1
ORDER BY u.team_id;
SELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (
18481
);
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1
AND u.deleted_at IS NULL
AND u.crm_required = 1
# AND u.team_id = 1
AND p.id IS NULL -- Move this condition to WHERE clause
ORDER BY u.team_id;
SELECT * FROM opportunities WHERE id = 20002609;
select * from teams where id = 1122; # Velatir, 29953 - [EMAIL]
select * from crm_configurations where id = 1060;
select * from crm_layouts where crm_configuration_id = 1060;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;
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 = 1122 and sa.provider = 'hubspot';
select * from opportunities where team_id = 1122 order by updated_at desc;
select * from crm_field_data where object_type = 'contact';
SELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262
SELECT
CONCAT(u.id, CASE WHEN u.id = t.own...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
85318
|
2921
|
39
|
2026-05-28T12:18:06.689033+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779970686689_m2.jpg...
|
PhpStorm
|
faVsco.js – HubspotJournalPollingService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…...
|
[{"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":"#12121 on JY-20963-fix-import-on-deleted-entity, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.11569149,"height":0.025538707},"on_screen":true,"help_text":"Pull request #12121 exists for current branch JY-20963-fix-import-on-deleted-entity","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.8374335,"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":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"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 'TextRelayServiceTest'","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 'TextRelayServiceTest'","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":"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":"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":"AXStaticText","text":"12","depth":4,"bounds":{"left":0.37865692,"top":0.15003991,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"9","depth":4,"bounds":{"left":0.39029256,"top":0.15003991,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39993352,"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.40724733,"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\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\Journal;\n\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Redis;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Repositories\\TeamRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\Webhook\\WebhookEventProcessor;\n\nclass HubspotJournalPollingService\n{\n private const string OFFSET_CACHE_KEY = 'hubspot_journal_offset';\n private const string POLLING_LOCK_KEY = 'hubspot_journal_polling_lock';\n private const string STOP_FLAG_KEY = 'hubspot_journal_stop_flag';\n private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds\n private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds\n private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data\n private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration\n private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep\n private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping\n private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit\n private const int MAX_BACKOFF_SECONDS = 300;\n private const int MAX_OFFSET_RETRIES = 3;\n private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes\n private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;\n private const int LOG_INTERVAL_CYCLES = 10;\n\n private bool $shouldStop = false;\n private int $consecutiveEmptyResults = 0;\n private array $offsetRetryCount = [];\n\n private int $cycleCount = 0;\n private int $startTime = 0;\n private int $lastLockRenewal = 0;\n\n private float $totalApiTime = 0;\n private float $totalDownloadTime = 0;\n private float $totalTransformTime = 0;\n private float $totalProcessTime = 0;\n\n private int $totalJournalFilesDownloaded = 0;\n private int $totalEventsProcessed = 0;\n private int $emptyJournalFiles = 0;\n private int $otherPortalSkipped = 0;\n\n public function __construct(\n private HubspotJournalApiClient $apiClient,\n private JournalEventTransformer $transformer,\n private WebhookEventProcessor $eventProcessor\n ) {\n }\n\n /**\n * Start continuous polling of the HubSpot journal\n */\n public function startPolling(): void\n {\n $this->startTime = time();\n $this->cycleCount = 0;\n $this->logPollingStart();\n\n if (! $this->acquirePollingLock()) {\n Log::warning('[HubSpot Journal Polling] Another polling process is already running');\n\n return;\n }\n\n try {\n while ($this->shouldContinuePolling()) {\n $this->cycleCount++;\n $this->executePollingCycle();\n }\n } catch (\\Throwable $e) {\n Log::error('[HubSpot Journal Polling] Error while polling', [\n 'error' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n } finally {\n $this->cleanupPollingSession();\n }\n }\n\n /**\n * Perform a single polling cycle\n * Returns true if work was found and processed, false if no work available, null if entry was skipped\n */\n public function pollOnce(): ?bool\n {\n $currentOffset = $this->getCurrentOffset();\n\n $apiStart = microtime(true);\n $apiResult = $currentOffset === null\n ? $this->apiClient->getLatestJournalEntry()\n : $this->apiClient->getNextJournalEntry($currentOffset);\n $this->totalApiTime += (microtime(true) - $apiStart) * 1000;\n\n if (! $apiResult->success) {\n return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');\n }\n\n if ($apiResult->data === null) {\n Log::info('[HubSpot Journal Polling] No data');\n\n return false;\n }\n\n $offset = $apiResult->data['currentOffset'];\n $s3Url = $apiResult->data['url'];\n $retryCount = $this->offsetRetryCount[$offset] ?? 0;\n\n $downloadStart = microtime(true);\n $downloadResult = $this->apiClient->downloadJournalFile($s3Url);\n $this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;\n\n if (! $downloadResult->success) {\n return $this->handleDownloadResult($downloadResult, $offset, $retryCount);\n }\n\n $this->totalJournalFilesDownloaded++;\n $journalData = $downloadResult->data;\n\n $transformStart = microtime(true);\n\n try {\n $transformedEvents = $this->transformer->transformJournalEvents($journalData);\n } catch (\\Exception $e) {\n return $this->handleTransformationFailure($offset, $retryCount, $e);\n }\n $this->totalTransformTime += (microtime(true) - $transformStart) * 1000;\n unset($journalData);\n\n $processStart = microtime(true);\n $eventsCount = count($transformedEvents);\n $this->totalEventsProcessed += $eventsCount;\n\n if ($eventsCount === 0) {\n $this->emptyJournalFiles++;\n } else {\n $this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');\n }\n $this->totalProcessTime += (microtime(true) - $processStart) * 1000;\n\n $this->updateOffset($offset);\n unset($transformedEvents);\n\n $this->logProgressIfNeeded();\n\n return $eventsCount > 0;\n }\n\n private function logProgressIfNeeded(): void\n {\n if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {\n return;\n }\n\n $cycles = $this->cycleCount;\n $avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;\n\n Log::info('[HubSpot Journal Polling] Progress', [\n 'cycles' => $cycles,\n 'files' => $this->totalJournalFilesDownloaded,\n 'events' => $this->totalEventsProcessed,\n 'empty_files' => $this->emptyJournalFiles,\n 'avg_ms' => round($avgTotal, 1),\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n ]);\n }\n\n /**\n * Get the current polling offset from Redis\n */\n private function getCurrentOffset(bool $cacheOnly = false): ?string\n {\n $offset = Redis::get(self::OFFSET_CACHE_KEY);\n\n if (! $offset && ! $cacheOnly) {\n $offset = $this->getDbOffset();\n }\n\n return $offset ? (string) $offset : null;\n }\n\n /**\n * Get the last stored offset from the database\n */\n public function getDbOffset(): ?string\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return null;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');\n\n Log::info('[HubSpot Journal Polling] Getting offset from database', [\n 'offset' => $offset,\n 'jiminny_team_id' => $jiminnyTeam->getId(),\n ]);\n\n return $offset;\n }\n\n /**\n * Reset the database offset\n */\n public function resetDbOffset(): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');\n\n Log::info('[HubSpot Journal Polling] Database offset reset');\n }\n\n private function getJiminnyTeam(): ?Team\n {\n $teamRepository = app(TeamRepository::class);\n\n return $teamRepository->getTeamBySlug('jiminny');\n }\n\n private function updateDbOffset(string $offset): void\n {\n $jiminnyTeam = $this->getJiminnyTeam();\n\n if (! $jiminnyTeam instanceof Team) {\n Log::error('[HubSpot Journal Polling] Jiminny team not found');\n\n return;\n }\n\n $crmRepository = app(CrmConfigurationRepository::class);\n\n $crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);\n }\n\n /**\n * Update the polling offset\n */\n private function updateOffset(string $offset): void\n {\n Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL\n $this->clearOffsetRetryCount($offset);\n }\n\n /**\n * Reset the polling offset\n */\n public function resetOffset(): void\n {\n Redis::del(self::OFFSET_CACHE_KEY);\n Log::info('[HubSpot Journal Polling] Reset polling offset');\n }\n\n /**\n * Set a specific offset (for manual override or recovery)\n */\n public function setOffset(string $offset): void\n {\n $this->updateOffset($offset);\n\n Log::warning('[HubSpot Journal Polling] Offset manually set', [\n 'offset' => $offset,\n ]);\n }\n\n /**\n * Get current polling status\n */\n public function getPollingStatus(): array\n {\n $lockDataJson = Redis::get(self::POLLING_LOCK_KEY);\n $lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;\n\n return [\n 'is_locked' => $lockData !== null,\n 'current_offset' => $this->getCurrentOffset(),\n 'lock_expires_at' => $lockData['expires_at'] ?? null,\n 'lock_acquired_at' => $lockData['acquired_at'] ?? null,\n ];\n }\n\n /**\n * Acquire exclusive polling lock to prevent multiple instances\n */\n private function acquirePollingLock(): bool\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n // Use atomic operation to set both lock and expiration data\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n ];\n\n // Use SETNX (SET if Not eXists) for atomic lock acquisition\n $lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');\n\n if ($lockAcquired) {\n $this->lastLockRenewal = time();\n Log::info('[HubSpot Journal Polling] Acquired polling lock', [\n 'expires_at' => $expiresAt,\n ]);\n }\n\n return (bool) $lockAcquired;\n }\n\n /**\n * Release the polling lock\n */\n private function releasePollingLock(): void\n {\n Redis::del(self::POLLING_LOCK_KEY);\n Log::info('[HubSpot Journal Polling] Released polling lock');\n }\n\n /**\n * Force release the polling lock (for emergency situations)\n */\n public function forceReleaseLock(): void\n {\n $this->releasePollingLock();\n Log::warning('[HubSpot Journal Polling] Force released polling lock');\n }\n\n /**\n * Signal the polling service to stop gracefully\n */\n public function requestStop(): void\n {\n Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL\n $this->shouldStop = true;\n Log::info('[HubSpot Journal Polling] Stop requested');\n }\n\n /**\n * Check if polling should stop\n */\n private function shouldStop(): bool\n {\n // Check local flag first (faster)\n if ($this->shouldStop) {\n return true;\n }\n\n // Check Redis flag (for external stop requests)\n if (Redis::exists(self::STOP_FLAG_KEY)) {\n $this->shouldStop = true;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Clear the stop flag and reset state to allow service to start\n */\n public function clearStopFlag(): void\n {\n Redis::del(self::STOP_FLAG_KEY);\n $this->shouldStop = false;\n $this->resetPollingState();\n\n Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');\n }\n\n /**\n * Reset polling state without clearing stop flag\n */\n private function resetPollingState(): void\n {\n $this->consecutiveEmptyResults = 0;\n $this->cycleCount = 0;\n $this->startTime = 0;\n $this->totalApiTime = 0;\n $this->totalDownloadTime = 0;\n $this->totalTransformTime = 0;\n $this->totalProcessTime = 0;\n $this->totalJournalFilesDownloaded = 0;\n $this->totalEventsProcessed = 0;\n $this->emptyJournalFiles = 0;\n $this->otherPortalSkipped = 0;\n }\n\n public function hasStopFlag(): bool\n {\n return (bool) Redis::exists(self::STOP_FLAG_KEY);\n }\n\n /**\n * Calculate adaptive sleep duration based on consecutive empty results\n */\n private function calculateAdaptiveSleep(): int\n {\n if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {\n return self::BASE_SLEEP_SECONDS; // 5 second\n }\n\n // Progressive backoff: 15s -> 30s -> 60s -> 120s (max)\n $delay = 3 * self::BASE_SLEEP_SECONDS;\n $sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));\n\n return min($sleepDuration, self::MAX_SLEEP_SECONDS);\n }\n\n private function logPollingStart(): void\n {\n Log::info('[HubSpot Journal Polling] Service starting', [\n 'memory_limit' => ini_get('memory_limit'),\n 'max_execution_time' => ini_get('max_execution_time'),\n 'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Log polling service end information\n */\n private function logPollingEnd(): void\n {\n $runtime = time() - $this->startTime;\n $cycles = max($this->cycleCount, 1);\n\n Log::info('[HubSpot Journal Polling] Service ending', [\n 'runtime_seconds' => $runtime,\n 'total_cycles' => $this->cycleCount,\n 'files_downloaded' => $this->totalJournalFilesDownloaded,\n 'empty_files' => $this->emptyJournalFiles,\n 'other_portal_skipped' => $this->otherPortalSkipped,\n 'total_events' => $this->totalEventsProcessed,\n 'events_per_file' => $this->totalJournalFilesDownloaded > 0\n ? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)\n : 0,\n 'avg_api_ms' => round($this->totalApiTime / $cycles, 1),\n 'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),\n 'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),\n 'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),\n 'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),\n ]);\n }\n\n /**\n * Improved stop condition check with clear reasoning\n */\n private function shouldContinuePolling(): bool\n {\n // Check explicit stop request first (fastest check)\n if ($this->shouldStop()) {\n Log::info('[HubSpot Journal Polling] Stop requested, ending polling');\n\n return false;\n }\n\n if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {\n Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [\n 'empty_results' => $this->consecutiveEmptyResults,\n 'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,\n ]);\n\n return false;\n }\n\n // Check runtime limit (4 minutes)\n $runtime = time() - $this->startTime;\n if ($runtime >= self::MAX_RUNTIME_SECONDS) {\n Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [\n 'runtime_seconds' => $runtime,\n 'runtime_minutes' => round($runtime / 60, 1),\n 'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,\n ]);\n\n return false;\n }\n\n // Check memory limits (prevent OOM)\n $currentMemory = memory_get_usage(true);\n $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));\n\n if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {\n Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [\n 'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),\n 'memory_limit' => ini_get('memory_limit'),\n 'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),\n ]);\n\n return false;\n }\n\n // Check for excessive cycles (prevent runaway processes)\n if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {\n Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [\n 'cycles' => $this->cycleCount,\n 'max_cycles' => self::MAX_CYCLES_DEFAULT,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Handle when work is found - reset counters and continue immediately\n */\n private function handleWorkFound(): void\n {\n $this->consecutiveEmptyResults = 0;\n }\n\n /**\n * Handle when no work is found - implement adaptive sleep\n */\n private function handleNoWorkFound(): void\n {\n $this->consecutiveEmptyResults++;\n $sleepDuration = $this->calculateAdaptiveSleep();\n\n Log::debug('[HubSpot Journal Polling] No work found, sleeping', [\n 'consecutive_empty' => $this->consecutiveEmptyResults,\n 'sleep_seconds' => $sleepDuration,\n 'cycle' => $this->cycleCount,\n ]);\n\n $this->responsiveSleep($sleepDuration);\n }\n\n /**\n * Sleep with responsive stop checking\n */\n private function responsiveSleep(int $seconds): void\n {\n for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {\n sleep(1);\n }\n }\n\n /**\n * Parse memory limit string to bytes\n */\n private function parseMemoryLimit(string $memoryLimit): int\n {\n if ($memoryLimit === '-1') {\n return 0; // Unlimited\n }\n\n $unit = strtolower(substr($memoryLimit, -1));\n $value = (int) substr($memoryLimit, 0, -1);\n\n return match ($unit) {\n 'g' => $value * 1024 * 1024 * 1024,\n 'm' => $value * 1024 * 1024,\n 'k' => $value * 1024,\n default => (int) $memoryLimit,\n };\n }\n\n private function executePollingCycle(): void\n {\n $this->renewLockIfNeeded();\n\n $result = $this->pollOnce();\n\n if ($result === true) {\n $this->handleWorkFound();\n } elseif ($result === false) {\n $this->handleNoWorkFound();\n }\n }\n\n private function renewLockIfNeeded(): void\n {\n $now = time();\n if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {\n $this->renewPollingLock();\n $this->lastLockRenewal = $now;\n }\n }\n\n private function renewPollingLock(): void\n {\n $expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();\n\n $lockData = [\n 'locked' => true,\n 'expires_at' => $expiresAt,\n 'acquired_at' => now()->toISOString(),\n 'renewed_at' => now()->toISOString(),\n 'cycle' => $this->cycleCount,\n ];\n\n Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));\n\n Log::debug('[HubSpot Journal Polling] Lock renewed', [\n 'expires_at' => $expiresAt,\n 'cycle' => $this->cycleCount,\n ]);\n }\n\n private function handleApiResult(JournalApiResult $result, string $context): bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_AUTH) {\n Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [\n 'context' => $context,\n ]);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {\n $waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);\n Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [\n 'wait_seconds' => $waitSeconds,\n ]);\n $this->responsiveSleep($waitSeconds);\n\n return false;\n }\n\n if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {\n Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [\n 'expired_offset' => $context,\n ]);\n $this->resetOffset();\n $this->resetDbOffset();\n\n return false;\n }\n\n if ($result->isPermanentFailure()) {\n Log::error('[HubSpot Journal Polling] Permanent API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->shouldStop = true;\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable API failure', [\n 'context' => $context,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Clean up polling session resources\n */\n private function cleanupPollingSession(): void\n {\n $this->logPollingEnd();\n $this->storeOffsetInDB();\n $this->releasePollingLock();\n $this->apiClient->clearPortalCache();\n $this->resetPollingState();\n }\n\n private function storeOffsetInDB(): void\n {\n // Save current offset to database before cleanup\n $currentOffset = $this->getCurrentOffset(cacheOnly: true);\n if ($currentOffset) {\n $this->updateDbOffset($currentOffset);\n\n Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [\n 'offset' => $currentOffset,\n ]);\n }\n }\n\n private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool\n {\n if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {\n $this->otherPortalSkipped++;\n $this->updateOffset($offset);\n\n return null;\n }\n\n if ($result->isPermanentFailure()) {\n Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'error' => $result->errorMessage,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [\n 'offset' => $offset,\n 'portal_id' => $result->portalId,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n ]);\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [\n 'offset' => $offset,\n 'failure_type' => $result->failureType,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n if ($result->retryAfterSeconds) {\n sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));\n }\n\n return false;\n }\n\n /**\n * Handle transformation failure with retry logic\n */\n private function handleTransformationFailure(string $offset, int $retryCount, \\Exception $exception): bool\n {\n $retryCount++;\n\n if ($retryCount >= self::MAX_OFFSET_RETRIES) {\n Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'error' => $exception->getMessage(),\n 'trace' => $exception->getTraceAsString(),\n ]);\n\n // Skip this offset to prevent infinite loop with corrupted data\n $this->updateOffset($offset);\n $this->clearOffsetRetryCount($offset);\n\n return false;\n }\n\n Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [\n 'offset' => $offset,\n 'retry_count' => $retryCount,\n 'max_retries' => self::MAX_OFFSET_RETRIES,\n 'error' => $exception->getMessage(),\n ]);\n\n $this->offsetRetryCount[$offset] = $retryCount;\n\n return false;\n }\n\n /**\n * Clear retry count for an offset after successful processing\n */\n private function clearOffsetRetryCount(string $offset): void\n {\n unset($this->offsetRetryCount[$offset]);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.41589096,"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.4245346,"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.43550533,"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.44414893,"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.45279256,"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}]...
|
5244664462083781043
|
-1183062619475730196
|
typing_pause
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
12
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Journal;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Jiminny\Models\Team;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Services\Crm\Hubspot\Webhook\WebhookEventProcessor;
class HubspotJournalPollingService
{
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const string [ENV_SECRET];
private const int LOCK_TTL_SECONDS = 120; // 2 minutes - renewed every 30 seconds
private const int LOCK_RENEWAL_INTERVAL = 30; // Renew lock every 30 seconds
private const int BASE_SLEEP_SECONDS = 5; // Base sleep when no data
private const int MAX_SLEEP_SECONDS = 120; // Maximum sleep duration
private const int EMPTY_THRESHOLD_FOR_BACKOFF = 3; // Empty results before increasing sleep
private const int MAX_CYCLES_DEFAULT = 3000; // Default maximum cycles before stopping
private const int MEMORY_CRITICAL_THRESHOLD = 90; // 90% of memory limit
private const int MAX_BACKOFF_SECONDS = 300;
private const int MAX_OFFSET_RETRIES = 3;
private const int MAX_RUNTIME_SECONDS = 240; // 4 minutes
private const int MAX_EMPTY_RESULTS_BEFORE_EXIT = 5;
private const int LOG_INTERVAL_CYCLES = 10;
private bool $shouldStop = false;
private int $consecutiveEmptyResults = 0;
private array $offsetRetryCount = [];
private int $cycleCount = 0;
private int $startTime = 0;
private int $lastLockRenewal = 0;
private float $totalApiTime = 0;
private float $totalDownloadTime = 0;
private float $totalTransformTime = 0;
private float $totalProcessTime = 0;
private int $totalJournalFilesDownloaded = 0;
private int $totalEventsProcessed = 0;
private int $emptyJournalFiles = 0;
private int $otherPortalSkipped = 0;
public function __construct(
private HubspotJournalApiClient $apiClient,
private JournalEventTransformer $transformer,
private WebhookEventProcessor $eventProcessor
) {
}
/**
* Start continuous polling of the HubSpot journal
*/
public function startPolling(): void
{
$this->startTime = time();
$this->cycleCount = 0;
$this->logPollingStart();
if (! $this->acquirePollingLock()) {
Log::warning('[HubSpot Journal Polling] Another polling process is already running');
return;
}
try {
while ($this->shouldContinuePolling()) {
$this->cycleCount++;
$this->executePollingCycle();
}
} catch (\Throwable $e) {
Log::error('[HubSpot Journal Polling] Error while polling', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
} finally {
$this->cleanupPollingSession();
}
}
/**
* Perform a single polling cycle
* Returns true if work was found and processed, false if no work available, null if entry was skipped
*/
public function pollOnce(): ?bool
{
$currentOffset = $this->getCurrentOffset();
$apiStart = microtime(true);
$apiResult = $currentOffset === null
? $this->apiClient->getLatestJournalEntry()
: $this->apiClient->getNextJournalEntry($currentOffset);
$this->totalApiTime += (microtime(true) - $apiStart) * 1000;
if (! $apiResult->success) {
return $this->handleApiResult($apiResult, $currentOffset ?? 'latest');
}
if ($apiResult->data === null) {
Log::info('[HubSpot Journal Polling] No data');
return false;
}
$offset = $apiResult->data['currentOffset'];
$s3Url = $apiResult->data['url'];
$retryCount = $this->offsetRetryCount[$offset] ?? 0;
$downloadStart = microtime(true);
$downloadResult = $this->apiClient->downloadJournalFile($s3Url);
$this->totalDownloadTime += (microtime(true) - $downloadStart) * 1000;
if (! $downloadResult->success) {
return $this->handleDownloadResult($downloadResult, $offset, $retryCount);
}
$this->totalJournalFilesDownloaded++;
$journalData = $downloadResult->data;
$transformStart = microtime(true);
try {
$transformedEvents = $this->transformer->transformJournalEvents($journalData);
} catch (\Exception $e) {
return $this->handleTransformationFailure($offset, $retryCount, $e);
}
$this->totalTransformTime += (microtime(true) - $transformStart) * 1000;
unset($journalData);
$processStart = microtime(true);
$eventsCount = count($transformedEvents);
$this->totalEventsProcessed += $eventsCount;
if ($eventsCount === 0) {
$this->emptyJournalFiles++;
} else {
$this->eventProcessor->processEvents($transformedEvents, '[HubSpot Journal Polling]');
}
$this->totalProcessTime += (microtime(true) - $processStart) * 1000;
$this->updateOffset($offset);
unset($transformedEvents);
$this->logProgressIfNeeded();
return $eventsCount > 0;
}
private function logProgressIfNeeded(): void
{
if ($this->cycleCount === 0 || $this->cycleCount % self::LOG_INTERVAL_CYCLES !== 0) {
return;
}
$cycles = $this->cycleCount;
$avgTotal = ($this->totalApiTime + $this->totalDownloadTime + $this->totalTransformTime + $this->totalProcessTime) / $cycles;
Log::info('[HubSpot Journal Polling] Progress', [
'cycles' => $cycles,
'files' => $this->totalJournalFilesDownloaded,
'events' => $this->totalEventsProcessed,
'empty_files' => $this->emptyJournalFiles,
'avg_ms' => round($avgTotal, 1),
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
]);
}
/**
* Get the current polling offset from Redis
*/
private function getCurrentOffset(bool $cacheOnly = false): ?string
{
$offset = Redis::get(self::OFFSET_CACHE_KEY);
if (! $offset && ! $cacheOnly) {
$offset = $this->getDbOffset();
}
return $offset ? (string) $offset : null;
}
/**
* Get the last stored offset from the database
*/
public function getDbOffset(): ?string
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return null;
}
$crmRepository = app(CrmConfigurationRepository::class);
$offset = $crmRepository->getSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset');
Log::info('[HubSpot Journal Polling] Getting offset from database', [
'offset' => $offset,
'jiminny_team_id' => $jiminnyTeam->getId(),
]);
return $offset;
}
/**
* Reset the database offset
*/
public function resetDbOffset(): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', '');
Log::info('[HubSpot Journal Polling] Database offset reset');
}
private function getJiminnyTeam(): ?Team
{
$teamRepository = app(TeamRepository::class);
return $teamRepository->getTeamBySlug('jiminny');
}
private function updateDbOffset(string $offset): void
{
$jiminnyTeam = $this->getJiminnyTeam();
if (! $jiminnyTeam instanceof Team) {
Log::error('[HubSpot Journal Polling] Jiminny team not found');
return;
}
$crmRepository = app(CrmConfigurationRepository::class);
$crmRepository->updateSetting($jiminnyTeam->getCrmConfiguration(), 'hubspot_journal_offset', $offset);
}
/**
* Update the polling offset
*/
private function updateOffset(string $offset): void
{
Redis::setex(self::OFFSET_CACHE_KEY, 259200, $offset); // 3 days TTL
$this->clearOffsetRetryCount($offset);
}
/**
* Reset the polling offset
*/
public function resetOffset(): void
{
Redis::del(self::OFFSET_CACHE_KEY);
Log::info('[HubSpot Journal Polling] Reset polling offset');
}
/**
* Set a specific offset (for manual override or recovery)
*/
public function setOffset(string $offset): void
{
$this->updateOffset($offset);
Log::warning('[HubSpot Journal Polling] Offset manually set', [
'offset' => $offset,
]);
}
/**
* Get current polling status
*/
public function getPollingStatus(): array
{
$lockDataJson = Redis::get(self::POLLING_LOCK_KEY);
$lockData = $lockDataJson ? json_decode($lockDataJson, true) : null;
return [
'is_locked' => $lockData !== null,
'current_offset' => $this->getCurrentOffset(),
'lock_expires_at' => $lockData['expires_at'] ?? null,
'lock_acquired_at' => $lockData['acquired_at'] ?? null,
];
}
/**
* Acquire exclusive polling lock to prevent multiple instances
*/
private function acquirePollingLock(): bool
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
// Use atomic operation to set both lock and expiration data
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
];
// Use SETNX (SET if Not eXists) for atomic lock acquisition
$lockAcquired = Redis::set(self::POLLING_LOCK_KEY, json_encode($lockData), 'EX', self::LOCK_TTL_SECONDS, 'NX');
if ($lockAcquired) {
$this->lastLockRenewal = time();
Log::info('[HubSpot Journal Polling] Acquired polling lock', [
'expires_at' => $expiresAt,
]);
}
return (bool) $lockAcquired;
}
/**
* Release the polling lock
*/
private function releasePollingLock(): void
{
Redis::del(self::POLLING_LOCK_KEY);
Log::info('[HubSpot Journal Polling] Released polling lock');
}
/**
* Force release the polling lock (for emergency situations)
*/
public function forceReleaseLock(): void
{
$this->releasePollingLock();
Log::warning('[HubSpot Journal Polling] Force released polling lock');
}
/**
* Signal the polling service to stop gracefully
*/
public function requestStop(): void
{
Redis::setex(self::STOP_FLAG_KEY, 300, '1'); // 5 minutes TTL
$this->shouldStop = true;
Log::info('[HubSpot Journal Polling] Stop requested');
}
/**
* Check if polling should stop
*/
private function shouldStop(): bool
{
// Check local flag first (faster)
if ($this->shouldStop) {
return true;
}
// Check Redis flag (for external stop requests)
if (Redis::exists(self::STOP_FLAG_KEY)) {
$this->shouldStop = true;
return true;
}
return false;
}
/**
* Clear the stop flag and reset state to allow service to start
*/
public function clearStopFlag(): void
{
Redis::del(self::STOP_FLAG_KEY);
$this->shouldStop = false;
$this->resetPollingState();
Log::info('[HubSpot Journal Polling] Stop flag cleared and state reset');
}
/**
* Reset polling state without clearing stop flag
*/
private function resetPollingState(): void
{
$this->consecutiveEmptyResults = 0;
$this->cycleCount = 0;
$this->startTime = 0;
$this->totalApiTime = 0;
$this->totalDownloadTime = 0;
$this->totalTransformTime = 0;
$this->totalProcessTime = 0;
$this->totalJournalFilesDownloaded = 0;
$this->totalEventsProcessed = 0;
$this->emptyJournalFiles = 0;
$this->otherPortalSkipped = 0;
}
public function hasStopFlag(): bool
{
return (bool) Redis::exists(self::STOP_FLAG_KEY);
}
/**
* Calculate adaptive sleep duration based on consecutive empty results
*/
private function calculateAdaptiveSleep(): int
{
if ($this->consecutiveEmptyResults < self::EMPTY_THRESHOLD_FOR_BACKOFF) {
return self::BASE_SLEEP_SECONDS; // 5 second
}
// Progressive backoff: 15s -> 30s -> 60s -> 120s (max)
$delay = 3 * self::BASE_SLEEP_SECONDS;
$sleepDuration = $delay * (2 ** ($this->consecutiveEmptyResults - self::EMPTY_THRESHOLD_FOR_BACKOFF));
return min($sleepDuration, self::MAX_SLEEP_SECONDS);
}
private function logPollingStart(): void
{
Log::info('[HubSpot Journal Polling] Service starting', [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'initial_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Log polling service end information
*/
private function logPollingEnd(): void
{
$runtime = time() - $this->startTime;
$cycles = max($this->cycleCount, 1);
Log::info('[HubSpot Journal Polling] Service ending', [
'runtime_seconds' => $runtime,
'total_cycles' => $this->cycleCount,
'files_downloaded' => $this->totalJournalFilesDownloaded,
'empty_files' => $this->emptyJournalFiles,
'other_portal_skipped' => $this->otherPortalSkipped,
'total_events' => $this->totalEventsProcessed,
'events_per_file' => $this->totalJournalFilesDownloaded > 0
? round($this->totalEventsProcessed / $this->totalJournalFilesDownloaded, 1)
: 0,
'avg_api_ms' => round($this->totalApiTime / $cycles, 1),
'avg_download_ms' => round($this->totalDownloadTime / $cycles, 1),
'avg_transform_ms' => round($this->totalTransformTime / $cycles, 1),
'avg_process_ms' => round($this->totalProcessTime / $cycles, 1),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
]);
}
/**
* Improved stop condition check with clear reasoning
*/
private function shouldContinuePolling(): bool
{
// Check explicit stop request first (fastest check)
if ($this->shouldStop()) {
Log::info('[HubSpot Journal Polling] Stop requested, ending polling');
return false;
}
if ($this->consecutiveEmptyResults >= self::MAX_EMPTY_RESULTS_BEFORE_EXIT) {
Log::warning('[HubSpot Journal Polling] Maximum empty results reached, stopping', [
'empty_results' => $this->consecutiveEmptyResults,
'max_empty_results' => self::MAX_EMPTY_RESULTS_BEFORE_EXIT,
]);
return false;
}
// Check runtime limit (4 minutes)
$runtime = time() - $this->startTime;
if ($runtime >= self::MAX_RUNTIME_SECONDS) {
Log::warning('[HubSpot Journal Polling] Maximum runtime reached, stopping', [
'runtime_seconds' => $runtime,
'runtime_minutes' => round($runtime / 60, 1),
'max_runtime_seconds' => self::MAX_RUNTIME_SECONDS,
]);
return false;
}
// Check memory limits (prevent OOM)
$currentMemory = memory_get_usage(true);
$memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
if ($memoryLimitBytes > 0 && $currentMemory > ($memoryLimitBytes * self::MEMORY_CRITICAL_THRESHOLD / 100)) {
Log::warning('[HubSpot Journal Polling] Memory limit approaching, stopping', [
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
'memory_limit' => ini_get('memory_limit'),
'usage_percent' => round(($currentMemory / $memoryLimitBytes) * 100, 1),
]);
return false;
}
// Check for excessive cycles (prevent runaway processes)
if ($this->cycleCount > self::MAX_CYCLES_DEFAULT) {
Log::warning('[HubSpot Journal Polling] Maximum cycles reached, stopping', [
'cycles' => $this->cycleCount,
'max_cycles' => self::MAX_CYCLES_DEFAULT,
]);
return false;
}
return true;
}
/**
* Handle when work is found - reset counters and continue immediately
*/
private function handleWorkFound(): void
{
$this->consecutiveEmptyResults = 0;
}
/**
* Handle when no work is found - implement adaptive sleep
*/
private function handleNoWorkFound(): void
{
$this->consecutiveEmptyResults++;
$sleepDuration = $this->calculateAdaptiveSleep();
Log::debug('[HubSpot Journal Polling] No work found, sleeping', [
'consecutive_empty' => $this->consecutiveEmptyResults,
'sleep_seconds' => $sleepDuration,
'cycle' => $this->cycleCount,
]);
$this->responsiveSleep($sleepDuration);
}
/**
* Sleep with responsive stop checking
*/
private function responsiveSleep(int $seconds): void
{
for ($i = 0; $i < $seconds && $this->shouldContinuePolling(); $i++) {
sleep(1);
}
}
/**
* Parse memory limit string to bytes
*/
private function parseMemoryLimit(string $memoryLimit): int
{
if ($memoryLimit === '-1') {
return 0; // Unlimited
}
$unit = strtolower(substr($memoryLimit, -1));
$value = (int) substr($memoryLimit, 0, -1);
return match ($unit) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => (int) $memoryLimit,
};
}
private function executePollingCycle(): void
{
$this->renewLockIfNeeded();
$result = $this->pollOnce();
if ($result === true) {
$this->handleWorkFound();
} elseif ($result === false) {
$this->handleNoWorkFound();
}
}
private function renewLockIfNeeded(): void
{
$now = time();
if ($now - $this->lastLockRenewal >= self::LOCK_RENEWAL_INTERVAL) {
$this->renewPollingLock();
$this->lastLockRenewal = $now;
}
}
private function renewPollingLock(): void
{
$expiresAt = now()->addSeconds(self::LOCK_TTL_SECONDS)->toISOString();
$lockData = [
'locked' => true,
'expires_at' => $expiresAt,
'acquired_at' => now()->toISOString(),
'renewed_at' => now()->toISOString(),
'cycle' => $this->cycleCount,
];
Redis::setex(self::POLLING_LOCK_KEY, self::LOCK_TTL_SECONDS, json_encode($lockData));
Log::debug('[HubSpot Journal Polling] Lock renewed', [
'expires_at' => $expiresAt,
'cycle' => $this->cycleCount,
]);
}
private function handleApiResult(JournalApiResult $result, string $context): bool
{
if ($result->failureType === JournalApiResult::FAILURE_AUTH) {
Log::warning('[HubSpot Journal Polling] Auth failure, will retry next cycle', [
'context' => $context,
]);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_RATE_LIMIT) {
$waitSeconds = min($result->retryAfterSeconds ?? 60, self::MAX_BACKOFF_SECONDS);
Log::warning('[HubSpot Journal Polling] Rate limit hit, waiting', [
'wait_seconds' => $waitSeconds,
]);
$this->responsiveSleep($waitSeconds);
return false;
}
if ($result->failureType === JournalApiResult::FAILURE_OFFSET_EXPIRED) {
Log::warning('[HubSpot Journal Polling] Offset expired, resetting to fetch latest data', [
'expired_offset' => $context,
]);
$this->resetOffset();
$this->resetDbOffset();
return false;
}
if ($result->isPermanentFailure()) {
Log::error('[HubSpot Journal Polling] Permanent API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->shouldStop = true;
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable API failure', [
'context' => $context,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Clean up polling session resources
*/
private function cleanupPollingSession(): void
{
$this->logPollingEnd();
$this->storeOffsetInDB();
$this->releasePollingLock();
$this->apiClient->clearPortalCache();
$this->resetPollingState();
}
private function storeOffsetInDB(): void
{
// Save current offset to database before cleanup
$currentOffset = $this->getCurrentOffset(cacheOnly: true);
if ($currentOffset) {
$this->updateDbOffset($currentOffset);
Log::info('[HubSpot Journal Polling] Saved offset to database on cleanup', [
'offset' => $currentOffset,
]);
}
}
private function handleDownloadResult(JournalApiResult $result, string $offset, int $retryCount): ?bool
{
if ($result->failureType === JournalApiResult::FAILURE_UNKNOWN_PORTAL) {
$this->otherPortalSkipped++;
$this->updateOffset($offset);
return null;
}
if ($result->isPermanentFailure()) {
Log::warning('[HubSpot Journal Polling] Permanent failure, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'error' => $result->errorMessage,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::error('[HubSpot Journal Polling] Max retries reached, skipping offset', [
'offset' => $offset,
'portal_id' => $result->portalId,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
]);
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::warning('[HubSpot Journal Polling] Retryable failure, will retry', [
'offset' => $offset,
'failure_type' => $result->failureType,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
]);
$this->offsetRetryCount[$offset] = $retryCount;
if ($result->retryAfterSeconds) {
sleep(min($result->retryAfterSeconds, self::MAX_BACKOFF_SECONDS));
}
return false;
}
/**
* Handle transformation failure with retry logic
*/
private function handleTransformationFailure(string $offset, int $retryCount, \Exception $exception): bool
{
$retryCount++;
if ($retryCount >= self::MAX_OFFSET_RETRIES) {
Log::critical('[HubSpot Journal Polling] Max transformation retries reached - SKIPPING offset', [
'offset' => $offset,
'retry_count' => $retryCount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Skip this offset to prevent infinite loop with corrupted data
$this->updateOffset($offset);
$this->clearOffsetRetryCount($offset);
return false;
}
Log::error('[HubSpot Journal Polling] Transformation failed - will retry', [
'offset' => $offset,
'retry_count' => $retryCount,
'max_retries' => self::MAX_OFFSET_RETRIES,
'error' => $exception->getMessage(),
]);
$this->offsetRetryCount[$offset] = $retryCount;
return false;
}
/**
* Clear retry count for an offset after successful processing
*/
private function clearOffsetRetryCount(string $offset): void
{
unset($this->offsetRetryCount[$offset]);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…...
|
85316
|
NULL
|
NULL
|
NULL
|