@hed-hog/core 0.0.399 → 0.0.400
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai/ai-agent.sort.d.ts +6 -0
- package/dist/ai/ai-agent.sort.d.ts.map +1 -0
- package/dist/ai/ai-agent.sort.js +21 -0
- package/dist/ai/ai-agent.sort.js.map +1 -0
- package/dist/ai/ai.controller.d.ts +4 -4
- package/dist/ai/ai.service.d.ts +35 -4
- package/dist/ai/ai.service.d.ts.map +1 -1
- package/dist/ai/ai.service.js +51 -137
- package/dist/ai/ai.service.js.map +1 -1
- package/dist/ai-instruction/ai-instruction.service.d.ts.map +1 -1
- package/dist/ai-instruction/ai-instruction.service.js +26 -23
- package/dist/ai-instruction/ai-instruction.service.js.map +1 -1
- package/dist/ai-instruction/ai-instruction.sort.d.ts +16 -0
- package/dist/ai-instruction/ai-instruction.sort.d.ts.map +1 -0
- package/dist/ai-instruction/ai-instruction.sort.js +32 -0
- package/dist/ai-instruction/ai-instruction.sort.js.map +1 -0
- package/dist/auth/auth.controller.d.ts +24 -8
- package/dist/auth/auth.controller.d.ts.map +1 -1
- package/dist/auth/auth.module.d.ts.map +1 -1
- package/dist/auth/auth.module.js +8 -1
- package/dist/auth/auth.module.js.map +1 -1
- package/dist/auth/auth.service.d.ts +72 -25
- package/dist/auth/auth.service.d.ts.map +1 -1
- package/dist/auth/auth.service.js +41 -4
- package/dist/auth/auth.service.js.map +1 -1
- package/dist/auth/guards/auth.guard.d.ts +3 -1
- package/dist/auth/guards/auth.guard.d.ts.map +1 -1
- package/dist/auth/guards/auth.guard.js +12 -3
- package/dist/auth/guards/auth.guard.js.map +1 -1
- package/dist/authorize/authorize.controller.d.ts +29 -5
- package/dist/authorize/authorize.controller.d.ts.map +1 -1
- package/dist/authorize/authorize.controller.js +120 -13
- package/dist/authorize/authorize.controller.js.map +1 -1
- package/dist/authorize/authorize.errors.d.ts +34 -0
- package/dist/authorize/authorize.errors.d.ts.map +1 -0
- package/dist/authorize/authorize.errors.js +37 -0
- package/dist/authorize/authorize.errors.js.map +1 -0
- package/dist/authorize/authorize.module.d.ts.map +1 -1
- package/dist/authorize/authorize.module.js +6 -1
- package/dist/authorize/authorize.module.js.map +1 -1
- package/dist/authorize/authorize.service.d.ts +77 -3
- package/dist/authorize/authorize.service.d.ts.map +1 -1
- package/dist/authorize/authorize.service.js +212 -21
- package/dist/authorize/authorize.service.js.map +1 -1
- package/dist/authorize/dto/authorize-info.dto.d.ts +10 -1
- package/dist/authorize/dto/authorize-info.dto.d.ts.map +1 -1
- package/dist/authorize/dto/authorize-info.dto.js +16 -1
- package/dist/authorize/dto/authorize-info.dto.js.map +1 -1
- package/dist/authorize/dto/consent.dto.d.ts +10 -1
- package/dist/authorize/dto/consent.dto.d.ts.map +1 -1
- package/dist/authorize/dto/consent.dto.js +16 -1
- package/dist/authorize/dto/consent.dto.js.map +1 -1
- package/dist/authorize/dto/token.dto.d.ts +12 -0
- package/dist/authorize/dto/token.dto.d.ts.map +1 -1
- package/dist/authorize/dto/token.dto.js +21 -1
- package/dist/authorize/dto/token.dto.js.map +1 -1
- package/dist/authorize/oauth-exception.filter.d.ts +17 -0
- package/dist/authorize/oauth-exception.filter.d.ts.map +1 -0
- package/dist/authorize/oauth-exception.filter.js +54 -0
- package/dist/authorize/oauth-exception.filter.js.map +1 -0
- package/dist/authorize/oauth-metadata.service.d.ts +39 -0
- package/dist/authorize/oauth-metadata.service.d.ts.map +1 -0
- package/dist/authorize/oauth-metadata.service.js +81 -0
- package/dist/authorize/oauth-metadata.service.js.map +1 -0
- package/dist/authorize/well-known.controller.d.ts +67 -0
- package/dist/authorize/well-known.controller.d.ts.map +1 -0
- package/dist/authorize/well-known.controller.js +126 -0
- package/dist/authorize/well-known.controller.js.map +1 -0
- package/dist/captcha/captcha.service.d.ts +32 -2
- package/dist/captcha/captcha.service.d.ts.map +1 -1
- package/dist/captcha/captcha.service.js +85 -12
- package/dist/captcha/captcha.service.js.map +1 -1
- package/dist/challenge/challenge.service.d.ts +2 -2
- package/dist/core.module.d.ts.map +1 -1
- package/dist/core.module.js +6 -0
- package/dist/core.module.js.map +1 -1
- package/dist/dashboard/dashboard/dashboard.controller.d.ts +9 -9
- package/dist/dashboard/dashboard/dashboard.service.d.ts +9 -9
- package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +3 -3
- package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +3 -3
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts +3 -3
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts +3 -3
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.js +2 -4
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.js.map +1 -1
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.sort.d.ts +8 -0
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.sort.d.ts.map +1 -0
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.sort.js +18 -0
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.sort.js.map +1 -0
- package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-item/dashboard-item.service.js +2 -4
- package/dist/dashboard/dashboard-item/dashboard-item.service.js.map +1 -1
- package/dist/dashboard/dashboard-item/dashboard-item.sort.d.ts +9 -0
- package/dist/dashboard/dashboard-item/dashboard-item.sort.d.ts.map +1 -0
- package/dist/dashboard/dashboard-item/dashboard-item.sort.js +19 -0
- package/dist/dashboard/dashboard-item/dashboard-item.sort.js.map +1 -0
- package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts +3 -3
- package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts +3 -3
- package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-role/dashboard-role.service.js +2 -4
- package/dist/dashboard/dashboard-role/dashboard-role.service.js.map +1 -1
- package/dist/dashboard/dashboard-role/dashboard-role.sort.d.ts +8 -0
- package/dist/dashboard/dashboard-role/dashboard-role.sort.d.ts.map +1 -0
- package/dist/dashboard/dashboard-role/dashboard-role.sort.js +16 -0
- package/dist/dashboard/dashboard-role/dashboard-role.sort.js.map +1 -0
- package/dist/file/file-upload-ticket.service.d.ts +160 -0
- package/dist/file/file-upload-ticket.service.d.ts.map +1 -0
- package/dist/file/file-upload-ticket.service.js +382 -0
- package/dist/file/file-upload-ticket.service.js.map +1 -0
- package/dist/file/file.controller.d.ts +24 -1
- package/dist/file/file.controller.d.ts.map +1 -1
- package/dist/file/file.controller.js +63 -2
- package/dist/file/file.controller.js.map +1 -1
- package/dist/file/file.module.d.ts.map +1 -1
- package/dist/file/file.module.js +14 -2
- package/dist/file/file.module.js.map +1 -1
- package/dist/file/file.service.d.ts +182 -6
- package/dist/file/file.service.d.ts.map +1 -1
- package/dist/file/file.service.js +386 -54
- package/dist/file/file.service.js.map +1 -1
- package/dist/file/filename-encoding.util.d.ts +24 -0
- package/dist/file/filename-encoding.util.d.ts.map +1 -0
- package/dist/file/filename-encoding.util.js +63 -0
- package/dist/file/filename-encoding.util.js.map +1 -0
- package/dist/file/image-optimization.service.d.ts +27 -0
- package/dist/file/image-optimization.service.d.ts.map +1 -1
- package/dist/file/image-optimization.service.js +63 -14
- package/dist/file/image-optimization.service.js.map +1 -1
- package/dist/file/mcp-file-source.schema.d.ts +33 -0
- package/dist/file/mcp-file-source.schema.d.ts.map +1 -0
- package/dist/file/mcp-file-source.schema.js +75 -0
- package/dist/file/mcp-file-source.schema.js.map +1 -0
- package/dist/file/mcp-file-source.service.d.ts +124 -0
- package/dist/file/mcp-file-source.service.d.ts.map +1 -0
- package/dist/file/mcp-file-source.service.js +262 -0
- package/dist/file/mcp-file-source.service.js.map +1 -0
- package/dist/file/mimetype-sniff.util.d.ts +62 -0
- package/dist/file/mimetype-sniff.util.d.ts.map +1 -0
- package/dist/file/mimetype-sniff.util.js +226 -0
- package/dist/file/mimetype-sniff.util.js.map +1 -0
- package/dist/file/remote-fetch.util.d.ts +35 -0
- package/dist/file/remote-fetch.util.d.ts.map +1 -0
- package/dist/file/remote-fetch.util.js +236 -0
- package/dist/file/remote-fetch.util.js.map +1 -0
- package/dist/impersonation/dto/start-impersonation.dto.d.ts +4 -1
- package/dist/impersonation/dto/start-impersonation.dto.d.ts.map +1 -1
- package/dist/impersonation/dto/start-impersonation.dto.js +1 -1
- package/dist/impersonation/dto/start-impersonation.dto.js.map +1 -1
- package/dist/impersonation/impersonation.service.d.ts +8 -3
- package/dist/impersonation/impersonation.service.d.ts.map +1 -1
- package/dist/impersonation/impersonation.service.js +14 -4
- package/dist/impersonation/impersonation.service.js.map +1 -1
- package/dist/incident/incident.controller.d.ts +2 -1
- package/dist/incident/incident.controller.d.ts.map +1 -1
- package/dist/incident/incident.controller.js +10 -6
- package/dist/incident/incident.controller.js.map +1 -1
- package/dist/incident/incident.service.d.ts +2 -0
- package/dist/incident/incident.service.d.ts.map +1 -1
- package/dist/incident/incident.service.js +4 -0
- package/dist/incident/incident.service.js.map +1 -1
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -1
- package/dist/integration-profile/integration-profile.secrets.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.secrets.js +7 -0
- package/dist/integration-profile/integration-profile.secrets.js.map +1 -1
- package/dist/integration-profile/integration-profile.service.d.ts +0 -13
- package/dist/integration-profile/integration-profile.service.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.service.js +5 -40
- package/dist/integration-profile/integration-profile.service.js.map +1 -1
- package/dist/integration-profile/integration-profile.utils.js +5 -5
- package/dist/integration-profile/integration-profile.utils.js.map +1 -1
- package/dist/integration-profile/testers/index.d.ts.map +1 -1
- package/dist/integration-profile/testers/index.js +2 -0
- package/dist/integration-profile/testers/index.js.map +1 -1
- package/dist/integration-profile/testers/shipping.testers.d.ts +3 -0
- package/dist/integration-profile/testers/shipping.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/shipping.testers.js +71 -0
- package/dist/integration-profile/testers/shipping.testers.js.map +1 -0
- package/dist/locale/locale.controller.d.ts +1 -1
- package/dist/locale/locale.service.d.ts +1 -1
- package/dist/mail/dto/send.dto.d.ts +6 -1
- package/dist/mail/dto/send.dto.d.ts.map +1 -1
- package/dist/mail/dto/send.dto.js.map +1 -1
- package/dist/mail/mail.service.d.ts +9 -0
- package/dist/mail/mail.service.d.ts.map +1 -1
- package/dist/mail/mail.service.js +34 -14
- package/dist/mail/mail.service.js.map +1 -1
- package/dist/mail-sent/mail-sent.controller.d.ts +1 -1
- package/dist/mail-sent/mail-sent.service.d.ts +1 -1
- package/dist/mail-sent/mail-sent.service.d.ts.map +1 -1
- package/dist/mail-sent/mail-sent.service.js +2 -1
- package/dist/mail-sent/mail-sent.service.js.map +1 -1
- package/dist/mail-sent/mail-sent.sort.d.ts +9 -0
- package/dist/mail-sent/mail-sent.sort.d.ts.map +1 -0
- package/dist/mail-sent/mail-sent.sort.js +19 -0
- package/dist/mail-sent/mail-sent.sort.js.map +1 -0
- package/dist/mcp/filters/mcp-challenge.filter.d.ts +20 -0
- package/dist/mcp/filters/mcp-challenge.filter.d.ts.map +1 -0
- package/dist/mcp/filters/mcp-challenge.filter.js +69 -0
- package/dist/mcp/filters/mcp-challenge.filter.js.map +1 -0
- package/dist/mcp/guards/mcp-auth.guard.d.ts +4 -1
- package/dist/mcp/guards/mcp-auth.guard.d.ts.map +1 -1
- package/dist/mcp/guards/mcp-auth.guard.js +19 -5
- package/dist/mcp/guards/mcp-auth.guard.js.map +1 -1
- package/dist/mcp/guards/mcp-permission.guard.d.ts +25 -3
- package/dist/mcp/guards/mcp-permission.guard.d.ts.map +1 -1
- package/dist/mcp/guards/mcp-permission.guard.js +46 -26
- package/dist/mcp/guards/mcp-permission.guard.js.map +1 -1
- package/dist/mcp/mcp-audit.service.d.ts +17 -0
- package/dist/mcp/mcp-audit.service.d.ts.map +1 -1
- package/dist/mcp/mcp-audit.service.js +32 -3
- package/dist/mcp/mcp-audit.service.js.map +1 -1
- package/dist/mcp/mcp-permission.service.d.ts +64 -0
- package/dist/mcp/mcp-permission.service.d.ts.map +1 -0
- package/dist/mcp/mcp-permission.service.js +178 -0
- package/dist/mcp/mcp-permission.service.js.map +1 -0
- package/dist/mcp/mcp-server.service.d.ts +3 -1
- package/dist/mcp/mcp-server.service.d.ts.map +1 -1
- package/dist/mcp/mcp-server.service.js +36 -15
- package/dist/mcp/mcp-server.service.js.map +1 -1
- package/dist/mcp/mcp.controller.d.ts.map +1 -1
- package/dist/mcp/mcp.controller.js +10 -2
- package/dist/mcp/mcp.controller.js.map +1 -1
- package/dist/mcp/mcp.module.d.ts.map +1 -1
- package/dist/mcp/mcp.module.js +5 -1
- package/dist/mcp/mcp.module.js.map +1 -1
- package/dist/mcp/tools/core-ai.mcp-tools.js +2 -9
- package/dist/mcp/tools/core-ai.mcp-tools.js.map +1 -1
- package/dist/mcp/tools/core-dashboard-items.mcp-tools.js +1 -1
- package/dist/mcp/tools/core-dashboard-items.mcp-tools.js.map +1 -1
- package/dist/mcp/tools/core-dashboard.mcp-tools.js +2 -9
- package/dist/mcp/tools/core-dashboard.mcp-tools.js.map +1 -1
- package/dist/mcp/tools/core-event-webhooks.mcp-tools.d.ts.map +1 -1
- package/dist/mcp/tools/core-event-webhooks.mcp-tools.js +2 -10
- package/dist/mcp/tools/core-event-webhooks.mcp-tools.js.map +1 -1
- package/dist/mcp/tools/core-files.mcp-tools.d.ts +17 -5
- package/dist/mcp/tools/core-files.mcp-tools.d.ts.map +1 -1
- package/dist/mcp/tools/core-files.mcp-tools.js +186 -24
- package/dist/mcp/tools/core-files.mcp-tools.js.map +1 -1
- package/dist/mcp/tools/core-integration-events.mcp-tools.js +2 -9
- package/dist/mcp/tools/core-integration-events.mcp-tools.js.map +1 -1
- package/dist/mcp/tools/core-mail-sent.mcp-tools.js +2 -14
- package/dist/mcp/tools/core-mail-sent.mcp-tools.js.map +1 -1
- package/dist/mcp/tools/core-mail.mcp-tools.js +2 -9
- package/dist/mcp/tools/core-mail.mcp-tools.js.map +1 -1
- package/dist/mcp/tools/core-menus.mcp-tools.d.ts.map +1 -1
- package/dist/mcp/tools/core-menus.mcp-tools.js +2 -10
- package/dist/mcp/tools/core-menus.mcp-tools.js.map +1 -1
- package/dist/mcp/tools/core-notifications.mcp-tools.d.ts.map +1 -1
- package/dist/mcp/tools/core-notifications.mcp-tools.js +0 -1
- package/dist/mcp/tools/core-notifications.mcp-tools.js.map +1 -1
- package/dist/mcp/tools/core-roles.mcp-tools.d.ts.map +1 -1
- package/dist/mcp/tools/core-roles.mcp-tools.js +2 -13
- package/dist/mcp/tools/core-roles.mcp-tools.js.map +1 -1
- package/dist/mcp/tools/core-routes.mcp-tools.d.ts.map +1 -1
- package/dist/mcp/tools/core-routes.mcp-tools.js +2 -11
- package/dist/mcp/tools/core-routes.mcp-tools.js.map +1 -1
- package/dist/mcp/tools/core-screens.mcp-tools.d.ts.map +1 -1
- package/dist/mcp/tools/core-screens.mcp-tools.js +2 -11
- package/dist/mcp/tools/core-screens.mcp-tools.js.map +1 -1
- package/dist/mcp/tools/core-sessions.mcp-tools.d.ts.map +1 -1
- package/dist/mcp/tools/core-sessions.mcp-tools.js +0 -2
- package/dist/mcp/tools/core-sessions.mcp-tools.js.map +1 -1
- package/dist/mcp/tools/core-settings.mcp-tools.d.ts.map +1 -1
- package/dist/mcp/tools/core-settings.mcp-tools.js +2 -10
- package/dist/mcp/tools/core-settings.mcp-tools.js.map +1 -1
- package/dist/mcp/tools/core-users.mcp-tools.js +2 -9
- package/dist/mcp/tools/core-users.mcp-tools.js.map +1 -1
- package/dist/mcp/tools/core-webhooks.mcp-tools.d.ts.map +1 -1
- package/dist/mcp/tools/core-webhooks.mcp-tools.js +2 -10
- package/dist/mcp/tools/core-webhooks.mcp-tools.js.map +1 -1
- package/dist/mcp/types/mcp-context.type.d.ts +9 -0
- package/dist/mcp/types/mcp-context.type.d.ts.map +1 -1
- package/dist/mcp-chat/mcp-chat.service.d.ts.map +1 -1
- package/dist/mcp-chat/mcp-chat.service.js +8 -0
- package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
- package/dist/meeting/meeting-provider.registry.d.ts +115 -0
- package/dist/meeting/meeting-provider.registry.d.ts.map +1 -1
- package/dist/meeting/meeting-provider.registry.js.map +1 -1
- package/dist/meeting/meeting.constants.d.ts +70 -0
- package/dist/meeting/meeting.constants.d.ts.map +1 -1
- package/dist/meeting/meeting.constants.js +9 -1
- package/dist/meeting/meeting.constants.js.map +1 -1
- package/dist/menu/menu.service.d.ts.map +1 -1
- package/dist/menu/menu.service.js +29 -9
- package/dist/menu/menu.service.js.map +1 -1
- package/dist/message-template/dto/update-message-template.dto.d.ts +18 -0
- package/dist/message-template/dto/update-message-template.dto.d.ts.map +1 -0
- package/dist/message-template/dto/update-message-template.dto.js +38 -0
- package/dist/message-template/dto/update-message-template.dto.js.map +1 -0
- package/dist/message-template/message-template-admin.service.d.ts +70 -0
- package/dist/message-template/message-template-admin.service.d.ts.map +1 -0
- package/dist/message-template/message-template-admin.service.js +226 -0
- package/dist/message-template/message-template-admin.service.js.map +1 -0
- package/dist/message-template/message-template.controller.d.ts +29 -0
- package/dist/message-template/message-template.controller.d.ts.map +1 -0
- package/dist/message-template/message-template.controller.js +101 -0
- package/dist/message-template/message-template.controller.js.map +1 -0
- package/dist/message-template/message-template.module.d.ts +10 -0
- package/dist/message-template/message-template.module.d.ts.map +1 -0
- package/dist/message-template/message-template.module.js +33 -0
- package/dist/message-template/message-template.module.js.map +1 -0
- package/dist/message-template/message-template.registry.d.ts +55 -0
- package/dist/message-template/message-template.registry.d.ts.map +1 -0
- package/dist/message-template/message-template.registry.js +64 -0
- package/dist/message-template/message-template.registry.js.map +1 -0
- package/dist/message-template/message-template.service.d.ts +59 -0
- package/dist/message-template/message-template.service.d.ts.map +1 -0
- package/dist/message-template/message-template.service.js +166 -0
- package/dist/message-template/message-template.service.js.map +1 -0
- package/dist/notification/notification.constants.d.ts +11 -0
- package/dist/notification/notification.constants.d.ts.map +1 -0
- package/dist/notification/notification.constants.js +14 -0
- package/dist/notification/notification.constants.js.map +1 -0
- package/dist/notification/notification.controller.d.ts +20 -20
- package/dist/notification/notification.module.d.ts.map +1 -1
- package/dist/notification/notification.module.js +3 -0
- package/dist/notification/notification.module.js.map +1 -1
- package/dist/notification/notification.service.d.ts +33 -21
- package/dist/notification/notification.service.d.ts.map +1 -1
- package/dist/notification/notification.service.js +36 -2
- package/dist/notification/notification.service.js.map +1 -1
- package/dist/oauth/oauth.controller.d.ts +4 -6
- package/dist/oauth/oauth.controller.d.ts.map +1 -1
- package/dist/oauth/oauth.controller.js +0 -1
- package/dist/oauth/oauth.controller.js.map +1 -1
- package/dist/oauth/oauth.errors.d.ts +15 -1
- package/dist/oauth/oauth.errors.d.ts.map +1 -1
- package/dist/oauth/oauth.errors.js +9 -1
- package/dist/oauth/oauth.errors.js.map +1 -1
- package/dist/oauth/oauth.module.d.ts.map +1 -1
- package/dist/oauth/oauth.module.js +6 -1
- package/dist/oauth/oauth.module.js.map +1 -1
- package/dist/oauth/oauth.service.d.ts +25 -5
- package/dist/oauth/oauth.service.d.ts.map +1 -1
- package/dist/oauth/oauth.service.js +63 -27
- package/dist/oauth/oauth.service.js.map +1 -1
- package/dist/oauth/providers/abstract.provider.d.ts +10 -0
- package/dist/oauth/providers/abstract.provider.d.ts.map +1 -1
- package/dist/oauth/providers/abstract.provider.js +39 -12
- package/dist/oauth/providers/abstract.provider.js.map +1 -1
- package/dist/oauth/providers/github.provider.d.ts +1 -1
- package/dist/oauth/providers/github.provider.d.ts.map +1 -1
- package/dist/oauth/providers/github.provider.js +48 -22
- package/dist/oauth/providers/github.provider.js.map +1 -1
- package/dist/oauth/providers/microsoft-entra-id.provider.d.ts.map +1 -1
- package/dist/oauth/providers/microsoft-entra-id.provider.js +6 -0
- package/dist/oauth/providers/microsoft-entra-id.provider.js.map +1 -1
- package/dist/observability/monitored-cron.d.ts +29 -12
- package/dist/observability/monitored-cron.d.ts.map +1 -1
- package/dist/observability/monitored-cron.js +131 -46
- package/dist/observability/monitored-cron.js.map +1 -1
- package/dist/pairing/pairing.service.d.ts +6 -2
- package/dist/pairing/pairing.service.d.ts.map +1 -1
- package/dist/profile/dto/change-password.dto.d.ts.map +1 -1
- package/dist/profile/dto/change-password.dto.js +4 -6
- package/dist/profile/dto/change-password.dto.js.map +1 -1
- package/dist/profile/profile.controller.d.ts +1 -1
- package/dist/profile/profile.service.d.ts +1 -1
- package/dist/profile/profile.service.d.ts.map +1 -1
- package/dist/profile/profile.service.js +9 -3
- package/dist/profile/profile.service.js.map +1 -1
- package/dist/realtime/realtime-tick.coalescer.d.ts +34 -0
- package/dist/realtime/realtime-tick.coalescer.d.ts.map +1 -0
- package/dist/realtime/realtime-tick.coalescer.js +60 -0
- package/dist/realtime/realtime-tick.coalescer.js.map +1 -0
- package/dist/realtime/realtime.service.d.ts +2 -0
- package/dist/realtime/realtime.service.d.ts.map +1 -1
- package/dist/realtime/realtime.service.js +22 -2
- package/dist/realtime/realtime.service.js.map +1 -1
- package/dist/redis/redis-kv.service.d.ts +90 -0
- package/dist/redis/redis-kv.service.d.ts.map +1 -0
- package/dist/redis/redis-kv.service.js +247 -0
- package/dist/redis/redis-kv.service.js.map +1 -0
- package/dist/redis/redis-master.d.ts +29 -0
- package/dist/redis/redis-master.d.ts.map +1 -0
- package/dist/redis/redis-master.js +81 -0
- package/dist/redis/redis-master.js.map +1 -0
- package/dist/redis/redis-pubsub.d.ts +12 -0
- package/dist/redis/redis-pubsub.d.ts.map +1 -1
- package/dist/redis/redis-pubsub.js +25 -2
- package/dist/redis/redis-pubsub.js.map +1 -1
- package/dist/redis/redis.module.d.ts +11 -0
- package/dist/redis/redis.module.d.ts.map +1 -0
- package/dist/redis/redis.module.js +30 -0
- package/dist/redis/redis.module.js.map +1 -0
- package/dist/session/session.controller.d.ts +6 -2
- package/dist/session/session.controller.d.ts.map +1 -1
- package/dist/session/session.service.d.ts +118 -15
- package/dist/session/session.service.d.ts.map +1 -1
- package/dist/session/session.service.js +251 -77
- package/dist/session/session.service.js.map +1 -1
- package/dist/setting/setting.controller.d.ts +5 -0
- package/dist/setting/setting.controller.d.ts.map +1 -1
- package/dist/setting/setting.service.d.ts +7 -0
- package/dist/setting/setting.service.d.ts.map +1 -1
- package/dist/setting/setting.service.js +38 -3
- package/dist/setting/setting.service.js.map +1 -1
- package/dist/task/task.service.d.ts +17 -0
- package/dist/task/task.service.d.ts.map +1 -1
- package/dist/task/task.service.js +48 -13
- package/dist/task/task.service.js.map +1 -1
- package/dist/token/token.service.d.ts +19 -1
- package/dist/token/token.service.d.ts.map +1 -1
- package/dist/token/token.service.js +39 -2
- package/dist/token/token.service.js.map +1 -1
- package/dist/user/user-credential.util.d.ts +10 -0
- package/dist/user/user-credential.util.d.ts.map +1 -1
- package/dist/user/user-credential.util.js +14 -1
- package/dist/user/user-credential.util.js.map +1 -1
- package/dist/user/user.service.d.ts +12 -4
- package/dist/user/user.service.d.ts.map +1 -1
- package/dist/user/user.service.js +2 -11
- package/dist/user/user.service.js.map +1 -1
- package/dist/user/user.sort.d.ts +16 -0
- package/dist/user/user.sort.d.ts.map +1 -0
- package/dist/user/user.sort.js +25 -0
- package/dist/user/user.sort.js.map +1 -0
- package/dist/utils/abbreviate-name.d.ts +13 -0
- package/dist/utils/abbreviate-name.d.ts.map +1 -0
- package/dist/utils/abbreviate-name.js +42 -0
- package/dist/utils/abbreviate-name.js.map +1 -0
- package/dist/utils/date-range-query.dto.d.ts +11 -0
- package/dist/utils/date-range-query.dto.d.ts.map +1 -0
- package/dist/utils/date-range-query.dto.js +63 -0
- package/dist/utils/date-range-query.dto.js.map +1 -0
- package/dist/utils/date-range.d.ts +96 -0
- package/dist/utils/date-range.d.ts.map +1 -0
- package/dist/utils/date-range.js +172 -0
- package/dist/utils/date-range.js.map +1 -0
- package/dist/webhook-integration/webhook-utils.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-utils.js +24 -0
- package/dist/webhook-integration/webhook-utils.js.map +1 -1
- package/dist/whatsapp-sender/whatsapp-recipients.d.ts +49 -0
- package/dist/whatsapp-sender/whatsapp-recipients.d.ts.map +1 -0
- package/dist/whatsapp-sender/whatsapp-recipients.js +66 -0
- package/dist/whatsapp-sender/whatsapp-recipients.js.map +1 -0
- package/dist/whatsapp-sender/whatsapp-sender.service.d.ts +25 -0
- package/dist/whatsapp-sender/whatsapp-sender.service.d.ts.map +1 -1
- package/dist/whatsapp-sender/whatsapp-sender.service.js +46 -5
- package/dist/whatsapp-sender/whatsapp-sender.service.js.map +1 -1
- package/hedhog/data/integration_provider.yaml +9 -0
- package/hedhog/data/integration_type.yaml +6 -0
- package/hedhog/data/locale.yaml +0 -1
- package/hedhog/data/mail.yaml +2 -2
- package/hedhog/data/mail_layout.yaml +6 -3
- package/hedhog/data/menu.yaml +25 -5
- package/hedhog/data/route.yaml +140 -0
- package/hedhog/data/setting_group.yaml +137 -3
- package/hedhog/frontend/app/access-log/page.tsx.ejs +47 -53
- package/hedhog/frontend/app/account/components/active-sessions.tsx.ejs +33 -8
- package/hedhog/frontend/app/account/components/mcp-tokens.tsx.ejs +54 -13
- package/hedhog/frontend/app/account/components/mfa-method-card.tsx.ejs +7 -1
- package/hedhog/frontend/app/account/components/recovery-codes-dialog.tsx.ejs +1 -0
- package/hedhog/frontend/app/account/components/tokens.tsx.ejs +47 -17
- package/hedhog/frontend/app/ai_agent/page.tsx.ejs +46 -5
- package/hedhog/frontend/app/ai_instruction/_components/ai-instruction-form.tsx.ejs +101 -30
- package/hedhog/frontend/app/ai_instruction/_components/ai-instruction-versions.tsx.ejs +24 -8
- package/hedhog/frontend/app/ai_instruction/page.tsx.ejs +80 -15
- package/hedhog/frontend/app/configurations/[slug]/components/image-optimization-action.tsx.ejs +166 -0
- package/hedhog/frontend/app/configurations/[slug]/components/setting-field.test.tsx.ejs +145 -45
- package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +99 -219
- package/hedhog/frontend/app/configurations/[slug]/page.tsx.ejs +19 -1
- package/hedhog/frontend/app/configurations/layout.tsx.ejs +19 -5
- package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +2 -8
- package/hedhog/frontend/app/dashboard/components/dynamic-widget.tsx.ejs +3 -0
- package/hedhog/frontend/app/dashboard/components/widget-wrapper.tsx.ejs +4 -0
- package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +43 -44
- package/hedhog/frontend/app/dashboard/management/tabs/component-roles-tab.tsx.ejs +41 -3
- package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +42 -2
- package/hedhog/frontend/app/dashboard/management/tabs/dashboard-roles-tab.tsx.ejs +41 -3
- package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +39 -4
- package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +42 -3
- package/hedhog/frontend/app/incidents/page.tsx.ejs +60 -31
- package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +45 -5
- package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +92 -15
- package/hedhog/frontend/app/mail/log/page.tsx.ejs +109 -61
- package/hedhog/frontend/app/mail/template/page.tsx.ejs +125 -23
- package/hedhog/frontend/app/mcp_chat/chat-components.tsx.ejs +13 -15
- package/hedhog/frontend/app/mcp_chat/page.tsx.ejs +116 -110
- package/hedhog/frontend/app/message-templates/_components/message-template-sheet.tsx.ejs +245 -0
- package/hedhog/frontend/app/message-templates/_lib/types.ts.ejs +24 -0
- package/hedhog/frontend/app/message-templates/page.tsx.ejs +182 -0
- package/hedhog/frontend/app/pages/_components/page-wizard-sheet.tsx.ejs +1 -0
- package/hedhog/frontend/app/pages/page.tsx.ejs +94 -9
- package/hedhog/frontend/app/roles/menus.tsx.ejs +4 -4
- package/hedhog/frontend/app/roles/routes.tsx.ejs +4 -4
- package/hedhog/frontend/app/roles/users.tsx.ejs +4 -4
- package/hedhog/frontend/app/setting-list/page.tsx.ejs +71 -9
- package/hedhog/frontend/app/users/page.tsx.ejs +77 -67
- package/hedhog/frontend/messages/en.json +183 -36
- package/hedhog/frontend/messages/pt.json +183 -36
- package/hedhog/table/cron_lock.yaml +17 -0
- package/hedhog/table/file.yaml +13 -1
- package/hedhog/table/file_upload_chunk.yaml +23 -0
- package/hedhog/table/file_upload_ticket.yaml +67 -0
- package/hedhog/table/mcp_tool_call_log.yaml +30 -22
- package/hedhog/table/message_template.yaml +33 -0
- package/hedhog/table/setting.yaml +8 -0
- package/hedhog/table/user_session.yaml +36 -1
- package/package.json +7 -5
- package/src/ai/ai-agent.sort.ts +21 -0
- package/src/ai/ai.service.spec.ts +173 -1
- package/src/ai/ai.service.ts +64 -193
- package/src/ai-instruction/ai-instruction.service.ts +32 -28
- package/src/ai-instruction/ai-instruction.sort.ts +32 -0
- package/src/auth/auth.module.ts +8 -1
- package/src/auth/auth.service.spec.ts +108 -1
- package/src/auth/auth.service.ts +59 -5
- package/src/auth/guards/auth.guard.ts +9 -1
- package/src/authorize/authorize.controller.ts +126 -11
- package/src/authorize/authorize.errors.ts +55 -0
- package/src/authorize/authorize.module.ts +6 -1
- package/src/authorize/authorize.service.spec.ts +379 -7
- package/src/authorize/authorize.service.ts +309 -23
- package/src/authorize/dto/authorize-info.dto.ts +21 -3
- package/src/authorize/dto/consent.dto.ts +21 -3
- package/src/authorize/dto/token.dto.ts +24 -2
- package/src/authorize/oauth-exception.filter.ts +58 -0
- package/src/authorize/oauth-metadata.service.ts +67 -0
- package/src/authorize/well-known.controller.spec.ts +84 -0
- package/src/authorize/well-known.controller.ts +96 -0
- package/src/captcha/captcha.service.spec.ts +211 -4
- package/src/captcha/captcha.service.ts +127 -10
- package/src/core.module.ts +6 -0
- package/src/dashboard/dashboard-component-role/dashboard-component-role.service.ts +2 -3
- package/src/dashboard/dashboard-component-role/dashboard-component-role.sort.ts +18 -0
- package/src/dashboard/dashboard-item/dashboard-item.service.ts +2 -3
- package/src/dashboard/dashboard-item/dashboard-item.sort.ts +19 -0
- package/src/dashboard/dashboard-role/dashboard-role.service.ts +2 -3
- package/src/dashboard/dashboard-role/dashboard-role.sort.ts +16 -0
- package/src/file/file-upload-ticket.service.spec.ts +422 -0
- package/src/file/file-upload-ticket.service.ts +513 -0
- package/src/file/file.controller.ts +65 -0
- package/src/file/file.module.ts +14 -2
- package/src/file/file.service.spec.ts +94 -0
- package/src/file/file.service.ts +433 -53
- package/src/file/filename-encoding.util.spec.ts +54 -0
- package/src/file/filename-encoding.util.ts +67 -0
- package/src/file/image-optimization.service.spec.ts +156 -19
- package/src/file/image-optimization.service.ts +106 -21
- package/src/file/mcp-file-source.schema.ts +83 -0
- package/src/file/mcp-file-source.service.spec.ts +473 -0
- package/src/file/mcp-file-source.service.ts +445 -0
- package/src/file/mimetype-sniff.util.spec.ts +126 -0
- package/src/file/mimetype-sniff.util.ts +265 -0
- package/src/file/remote-fetch.util.spec.ts +117 -0
- package/src/file/remote-fetch.util.ts +330 -0
- package/src/impersonation/dto/start-impersonation.dto.ts +5 -2
- package/src/impersonation/impersonation.service.spec.ts +24 -1
- package/src/impersonation/impersonation.service.ts +14 -4
- package/src/incident/incident.controller.ts +9 -1
- package/src/incident/incident.service.ts +7 -0
- package/src/index.ts +17 -0
- package/src/integration-profile/integration-profile.secrets.ts +7 -0
- package/src/integration-profile/integration-profile.service.ts +7 -64
- package/src/integration-profile/integration-profile.utils.spec.ts +9 -0
- package/src/integration-profile/integration-profile.utils.ts +5 -5
- package/src/integration-profile/testers/index.ts +2 -0
- package/src/integration-profile/testers/remaining.testers.spec.ts +79 -0
- package/src/integration-profile/testers/shipping.testers.ts +94 -0
- package/src/mail/dto/send.dto.ts +6 -1
- package/src/mail/mail-template.spec.ts +58 -1
- package/src/mail/mail.service.spec.ts +112 -0
- package/src/mail/mail.service.ts +42 -4
- package/src/mail-sent/mail-sent.service.ts +3 -0
- package/src/mail-sent/mail-sent.sort.ts +19 -0
- package/src/mcp/filters/mcp-challenge.filter.spec.ts +76 -0
- package/src/mcp/filters/mcp-challenge.filter.ts +70 -0
- package/src/mcp/guards/mcp-auth.guard.spec.ts +51 -2
- package/src/mcp/guards/mcp-auth.guard.ts +24 -1
- package/src/mcp/guards/mcp-permission.guard.spec.ts +109 -15
- package/src/mcp/guards/mcp-permission.guard.ts +76 -51
- package/src/mcp/mcp-audit.service.spec.ts +107 -0
- package/src/mcp/mcp-audit.service.ts +37 -1
- package/src/mcp/mcp-permission.service.spec.ts +197 -0
- package/src/mcp/mcp-permission.service.ts +184 -0
- package/src/mcp/mcp-server.service.spec.ts +84 -2
- package/src/mcp/mcp-server.service.ts +111 -87
- package/src/mcp/mcp.controller.ts +8 -1
- package/src/mcp/mcp.module.ts +5 -1
- package/src/mcp/tools/core-ai.mcp-tools.ts +1 -1
- package/src/mcp/tools/core-dashboard-items.mcp-tools.ts +1 -1
- package/src/mcp/tools/core-dashboard.mcp-tools.ts +1 -1
- package/src/mcp/tools/core-event-webhooks.mcp-tools.ts +1 -2
- package/src/mcp/tools/core-files.mcp-tools.ts +202 -22
- package/src/mcp/tools/core-integration-events.mcp-tools.ts +1 -1
- package/src/mcp/tools/core-mail-sent.mcp-tools.ts +1 -1
- package/src/mcp/tools/core-mail.mcp-tools.ts +1 -1
- package/src/mcp/tools/core-menus.mcp-tools.ts +1 -2
- package/src/mcp/tools/core-notifications.mcp-tools.ts +0 -1
- package/src/mcp/tools/core-roles.mcp-tools.ts +1 -5
- package/src/mcp/tools/core-routes.mcp-tools.ts +1 -3
- package/src/mcp/tools/core-screens.mcp-tools.ts +1 -3
- package/src/mcp/tools/core-sessions.mcp-tools.ts +0 -2
- package/src/mcp/tools/core-settings.mcp-tools.ts +1 -2
- package/src/mcp/tools/core-users.mcp-tools.ts +1 -1
- package/src/mcp/tools/core-webhooks.mcp-tools.ts +1 -2
- package/src/mcp/types/mcp-context.type.ts +15 -6
- package/src/mcp-chat/mcp-chat.service.ts +8 -0
- package/src/meeting/meeting-provider.registry.ts +121 -0
- package/src/meeting/meeting.constants.ts +74 -0
- package/src/menu/menu.service.spec.ts +89 -0
- package/src/menu/menu.service.ts +29 -11
- package/src/message-template/dto/update-message-template.dto.ts +22 -0
- package/src/message-template/message-template-admin.service.spec.ts +163 -0
- package/src/message-template/message-template-admin.service.ts +239 -0
- package/src/message-template/message-template.controller.ts +52 -0
- package/src/message-template/message-template.module.ts +21 -0
- package/src/message-template/message-template.registry.spec.ts +62 -0
- package/src/message-template/message-template.registry.ts +125 -0
- package/src/message-template/message-template.service.spec.ts +149 -0
- package/src/message-template/message-template.service.ts +149 -0
- package/src/notification/notification.constants.ts +10 -0
- package/src/notification/notification.module.ts +3 -0
- package/src/notification/notification.service.spec.ts +75 -1
- package/src/notification/notification.service.ts +39 -1
- package/src/oauth/oauth.controller.ts +0 -1
- package/src/oauth/oauth.errors.ts +7 -0
- package/src/oauth/oauth.module.ts +6 -1
- package/src/oauth/oauth.service.spec.ts +58 -7
- package/src/oauth/oauth.service.ts +71 -32
- package/src/oauth/providers/abstract.provider.spec.ts +192 -0
- package/src/oauth/providers/abstract.provider.ts +46 -14
- package/src/oauth/providers/github.provider.ts +57 -34
- package/src/oauth/providers/microsoft-entra-id.provider.ts +8 -1
- package/src/observability/monitored-cron.spec.ts +164 -31
- package/src/observability/monitored-cron.ts +154 -62
- package/src/profile/dto/change-password.dto.ts +25 -7
- package/src/profile/profile.service.spec.ts +97 -0
- package/src/profile/profile.service.ts +9 -3
- package/src/realtime/realtime-tick.coalescer.spec.ts +92 -0
- package/src/realtime/realtime-tick.coalescer.ts +60 -0
- package/src/realtime/realtime.service.spec.ts +33 -0
- package/src/realtime/realtime.service.ts +28 -2
- package/src/redis/redis-kv.service.ts +261 -0
- package/src/redis/redis-master.spec.ts +111 -0
- package/src/redis/redis-master.ts +86 -0
- package/src/redis/redis-pubsub.ts +27 -3
- package/src/redis/redis.module.ts +17 -0
- package/src/session/session.service.spec.ts +121 -42
- package/src/session/session.service.ts +279 -82
- package/src/setting/setting.service.ts +44 -3
- package/src/task/task.service.spec.ts +41 -0
- package/src/task/task.service.ts +53 -13
- package/src/token/token.service.spec.ts +90 -0
- package/src/token/token.service.ts +48 -2
- package/src/user/user-credential.util.ts +14 -5
- package/src/user/user.service.ts +2 -12
- package/src/user/user.sort.ts +25 -0
- package/src/utils/abbreviate-name.spec.ts +50 -0
- package/src/utils/abbreviate-name.ts +44 -0
- package/src/utils/date-range-query.dto.ts +56 -0
- package/src/utils/date-range.spec.ts +175 -0
- package/src/utils/date-range.ts +246 -0
- package/src/webhook-integration/webhook-utils.spec.ts +67 -0
- package/src/webhook-integration/webhook-utils.ts +26 -0
- package/src/whatsapp-sender/whatsapp-recipients.spec.ts +89 -0
- package/src/whatsapp-sender/whatsapp-recipients.ts +83 -0
- package/src/whatsapp-sender/whatsapp-sender.service.spec.ts +112 -0
- package/src/whatsapp-sender/whatsapp-sender.service.ts +58 -5
- package/hedhog/query/device_pairing_request.sql +0 -46
- package/hedhog/query/impersonation_grant.sql +0 -71
- package/hedhog/query/oauth_authorize_pkce.sql +0 -58
- package/hedhog/query/oauth_mobile_state_token.sql +0 -30
- package/hedhog/query/oauth_web_ref_token.sql +0 -25
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
|
|
1
|
+
import { describe, it, expect, jest, beforeEach, afterEach } from '@jest/globals';
|
|
2
2
|
|
|
3
3
|
const withMonitor = jest.fn((_slug: string, callback: () => any) => callback());
|
|
4
4
|
const startSpan = jest.fn((_opts: any, callback: () => any) => callback());
|
|
5
|
+
const startNewTrace = jest.fn((callback: () => any) => callback());
|
|
5
6
|
const captureException = jest.fn();
|
|
6
7
|
const withScope = jest.fn((cb: (scope: any) => void) => {
|
|
7
8
|
const scope = { setTag: jest.fn() };
|
|
@@ -9,41 +10,83 @@ const withScope = jest.fn((cb: (scope: any) => void) => {
|
|
|
9
10
|
return scope;
|
|
10
11
|
});
|
|
11
12
|
|
|
12
|
-
jest.mock('@sentry/nestjs', () => ({
|
|
13
|
+
jest.mock('@sentry/nestjs', () => ({
|
|
14
|
+
withMonitor,
|
|
15
|
+
startSpan,
|
|
16
|
+
startNewTrace,
|
|
17
|
+
captureException,
|
|
18
|
+
withScope,
|
|
19
|
+
}));
|
|
13
20
|
|
|
14
21
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
15
22
|
const { runMonitoredCron } = require('./monitored-cron');
|
|
16
23
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
type FakeOptions = {
|
|
25
|
+
/** `false` simula outro pod segurando a lease (nenhuma linha devolvida). */
|
|
26
|
+
won?: boolean;
|
|
27
|
+
claimThrows?: boolean;
|
|
28
|
+
releaseThrows?: boolean;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* O Prisma chama a tagged template como `$queryRaw(strings, ...values)`, então os
|
|
33
|
+
* parâmetros do SQL chegam aqui na ordem em que aparecem na query: o claim manda
|
|
34
|
+
* [slug, holder, leaseSeconds] e o release manda [slug, holder].
|
|
35
|
+
*/
|
|
36
|
+
function fakePrisma(options: FakeOptions = {}) {
|
|
37
|
+
const claims: any[][] = [];
|
|
38
|
+
const releases: any[][] = [];
|
|
39
|
+
|
|
40
|
+
const $queryRaw = jest.fn(async (_strings: any, ...values: any[]) => {
|
|
41
|
+
claims.push(values);
|
|
42
|
+
if (options.claimThrows) throw new Error('Unable to start a transaction in the given time.');
|
|
43
|
+
return options.won === false ? [] : [{ won: 1 }];
|
|
22
44
|
});
|
|
23
|
-
|
|
45
|
+
|
|
46
|
+
const $executeRaw = jest.fn(async (_strings: any, ...values: any[]) => {
|
|
47
|
+
releases.push(values);
|
|
48
|
+
if (options.releaseThrows) throw new Error('release falhou');
|
|
49
|
+
return 1;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return { $queryRaw, $executeRaw, claims, releases };
|
|
24
53
|
}
|
|
25
54
|
|
|
55
|
+
const BASE = { slug: 'meu-cron', schedule: '* * * * *' };
|
|
56
|
+
|
|
26
57
|
describe('runMonitoredCron', () => {
|
|
58
|
+
const originalSlugs = process.env.SENTRY_CRON_MONITOR_SLUGS;
|
|
59
|
+
|
|
27
60
|
beforeEach(() => {
|
|
28
61
|
jest.clearAllMocks();
|
|
62
|
+
// O default de produção libera check-in só para um slug; os casos abaixo
|
|
63
|
+
// descrevem o comportamento com o monitor ligado, então valem para todos.
|
|
64
|
+
process.env.SENTRY_CRON_MONITOR_SLUGS = '*';
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
if (originalSlugs === undefined) delete process.env.SENTRY_CRON_MONITOR_SLUGS;
|
|
69
|
+
else process.env.SENTRY_CRON_MONITOR_SLUGS = originalSlugs;
|
|
29
70
|
});
|
|
30
71
|
|
|
31
72
|
it('retorna undefined sem executar work() quando outro pod já ganhou o tick', async () => {
|
|
32
|
-
const prisma = fakePrisma(false);
|
|
73
|
+
const prisma = fakePrisma({ won: false });
|
|
33
74
|
const work = jest.fn();
|
|
34
75
|
|
|
35
|
-
const result = await runMonitoredCron(prisma,
|
|
76
|
+
const result = await runMonitoredCron(prisma, BASE, work);
|
|
36
77
|
|
|
37
78
|
expect(result).toBeUndefined();
|
|
38
79
|
expect(work).not.toHaveBeenCalled();
|
|
39
80
|
expect(withMonitor).not.toHaveBeenCalled();
|
|
81
|
+
// Perder o tick não pode mexer na lease de quem venceu.
|
|
82
|
+
expect(prisma.$executeRaw).not.toHaveBeenCalled();
|
|
40
83
|
});
|
|
41
84
|
|
|
42
85
|
it('executa work() e devolve o resultado quando ganha o tick', async () => {
|
|
43
|
-
const prisma = fakePrisma(
|
|
86
|
+
const prisma = fakePrisma();
|
|
44
87
|
const work = jest.fn().mockResolvedValue({ ok: true });
|
|
45
88
|
|
|
46
|
-
const result = await runMonitoredCron(prisma,
|
|
89
|
+
const result = await runMonitoredCron(prisma, BASE, work);
|
|
47
90
|
|
|
48
91
|
expect(work).toHaveBeenCalledTimes(1);
|
|
49
92
|
expect(withMonitor).toHaveBeenCalledWith(
|
|
@@ -54,44 +97,61 @@ describe('runMonitoredCron', () => {
|
|
|
54
97
|
expect(result).toEqual({ ok: true });
|
|
55
98
|
});
|
|
56
99
|
|
|
57
|
-
it('
|
|
58
|
-
const prisma = fakePrisma(
|
|
100
|
+
it('libera a lease com o MESMO holder que a tomou', async () => {
|
|
101
|
+
const prisma = fakePrisma();
|
|
102
|
+
|
|
103
|
+
await runMonitoredCron(prisma, BASE, jest.fn().mockResolvedValue(undefined));
|
|
104
|
+
|
|
105
|
+
const [claimSlug, claimHolder] = prisma.claims[0];
|
|
106
|
+
const [releaseSlug, releaseHolder] = prisma.releases[0];
|
|
107
|
+
|
|
108
|
+
expect(claimSlug).toBe('meu-cron');
|
|
109
|
+
expect(releaseSlug).toBe('meu-cron');
|
|
110
|
+
expect(releaseHolder).toBe(claimHolder);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('usa um holder diferente a cada execução, para um release atrasado não apagar a lease do tick seguinte', async () => {
|
|
114
|
+
const prisma = fakePrisma();
|
|
115
|
+
const work = jest.fn().mockResolvedValue(undefined);
|
|
116
|
+
|
|
117
|
+
await runMonitoredCron(prisma, BASE, work);
|
|
118
|
+
await runMonitoredCron(prisma, BASE, work);
|
|
119
|
+
|
|
120
|
+
expect(prisma.claims[0][1]).not.toBe(prisma.claims[1][1]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('NUNCA relança quando work() falha — captura no Sentry, libera a lease e retorna undefined', async () => {
|
|
124
|
+
const prisma = fakePrisma();
|
|
59
125
|
const boom = new Error('cron falhou');
|
|
60
126
|
const work = jest.fn().mockRejectedValue(boom);
|
|
61
127
|
|
|
62
|
-
await expect(
|
|
63
|
-
runMonitoredCron(prisma, { slug: 'meu-cron', schedule: '* * * * *' }, work),
|
|
64
|
-
).resolves.toBeUndefined();
|
|
128
|
+
await expect(runMonitoredCron(prisma, BASE, work)).resolves.toBeUndefined();
|
|
65
129
|
|
|
66
130
|
expect(captureException).toHaveBeenCalledWith(
|
|
67
131
|
boom,
|
|
68
132
|
expect.objectContaining({ mechanism: { handled: true, type: 'hedhog.cron' } }),
|
|
69
133
|
);
|
|
134
|
+
// A lease não pode ficar presa até vencer só porque o trabalho deu erro.
|
|
135
|
+
expect(prisma.$executeRaw).toHaveBeenCalledTimes(1);
|
|
70
136
|
});
|
|
71
137
|
|
|
72
|
-
it('dimensiona
|
|
73
|
-
const prisma = fakePrisma(
|
|
74
|
-
const work = jest.fn().mockResolvedValue(undefined);
|
|
138
|
+
it('dimensiona a validade da lease a partir de maxRuntimeMinutes', async () => {
|
|
139
|
+
const prisma = fakePrisma();
|
|
75
140
|
|
|
76
141
|
await runMonitoredCron(
|
|
77
142
|
prisma,
|
|
78
|
-
{
|
|
79
|
-
|
|
143
|
+
{ ...BASE, maxRuntimeMinutes: 2 },
|
|
144
|
+
jest.fn().mockResolvedValue(undefined),
|
|
80
145
|
);
|
|
81
146
|
|
|
82
|
-
expect(prisma
|
|
83
|
-
expect.any(Function),
|
|
84
|
-
{ timeout: 2 * 60_000 + 30_000, maxWait: 20_000 },
|
|
85
|
-
);
|
|
147
|
+
expect(prisma.claims[0][2]).toBe(2 * 60 + 30);
|
|
86
148
|
});
|
|
87
149
|
|
|
88
|
-
it('NUNCA relança quando a
|
|
89
|
-
const prisma = fakePrisma(
|
|
150
|
+
it('NUNCA relança quando a eleição falha (ex.: banco indisponível) — captura no Sentry', async () => {
|
|
151
|
+
const prisma = fakePrisma({ claimThrows: true });
|
|
90
152
|
const work = jest.fn();
|
|
91
153
|
|
|
92
|
-
await expect(
|
|
93
|
-
runMonitoredCron(prisma, { slug: 'meu-cron', schedule: '* * * * *' }, work),
|
|
94
|
-
).resolves.toBeUndefined();
|
|
154
|
+
await expect(runMonitoredCron(prisma, BASE, work)).resolves.toBeUndefined();
|
|
95
155
|
|
|
96
156
|
expect(work).not.toHaveBeenCalled();
|
|
97
157
|
expect(captureException).toHaveBeenCalledWith(
|
|
@@ -99,4 +159,77 @@ describe('runMonitoredCron', () => {
|
|
|
99
159
|
expect.objectContaining({ mechanism: { handled: true, type: 'hedhog.cron' } }),
|
|
100
160
|
);
|
|
101
161
|
});
|
|
162
|
+
|
|
163
|
+
it('falha ao liberar a lease não derruba o cron nem mascara o resultado de work()', async () => {
|
|
164
|
+
const prisma = fakePrisma({ releaseThrows: true });
|
|
165
|
+
const work = jest.fn().mockResolvedValue({ ok: true });
|
|
166
|
+
|
|
167
|
+
await expect(runMonitoredCron(prisma, BASE, work)).resolves.toEqual({ ok: true });
|
|
168
|
+
|
|
169
|
+
expect(captureException).not.toHaveBeenCalled();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* O plano do Sentry cobre UM monitor de cron e o projeto tem cinco. Check-in de
|
|
174
|
+
* slug sem cota é descartado na entrada, então o que se protege aqui é que a
|
|
175
|
+
* ausência dele não leve junto o trabalho, o span nem o relato de erro.
|
|
176
|
+
*/
|
|
177
|
+
describe('allowlist de check-in (SENTRY_CRON_MONITOR_SLUGS)', () => {
|
|
178
|
+
it('emite check-in para slug listado', async () => {
|
|
179
|
+
process.env.SENTRY_CRON_MONITOR_SLUGS = 'outro-cron,meu-cron';
|
|
180
|
+
const prisma = fakePrisma();
|
|
181
|
+
|
|
182
|
+
await runMonitoredCron(prisma, BASE, jest.fn().mockResolvedValue({ ok: true }));
|
|
183
|
+
|
|
184
|
+
expect(withMonitor).toHaveBeenCalledTimes(1);
|
|
185
|
+
expect(startNewTrace).not.toHaveBeenCalled();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('slug fora da lista roda work() e o span, sem check-in', async () => {
|
|
189
|
+
process.env.SENTRY_CRON_MONITOR_SLUGS = 'outro-cron';
|
|
190
|
+
const prisma = fakePrisma();
|
|
191
|
+
const work = jest.fn().mockResolvedValue({ ok: true });
|
|
192
|
+
|
|
193
|
+
const result = await runMonitoredCron(prisma, BASE, work);
|
|
194
|
+
|
|
195
|
+
expect(result).toEqual({ ok: true });
|
|
196
|
+
expect(work).toHaveBeenCalledTimes(1);
|
|
197
|
+
expect(withMonitor).not.toHaveBeenCalled();
|
|
198
|
+
// O trace continua isolado por execução, papel que o `isolateTrace` do
|
|
199
|
+
// withMonitor cumpre no outro ramo.
|
|
200
|
+
expect(startNewTrace).toHaveBeenCalledTimes(1);
|
|
201
|
+
expect(startSpan).toHaveBeenCalledWith(
|
|
202
|
+
expect.objectContaining({ name: 'meu-cron', op: 'cron.hedhog' }),
|
|
203
|
+
expect.any(Function),
|
|
204
|
+
);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('slug fora da lista continua liberando a lease e reportando erro', async () => {
|
|
208
|
+
process.env.SENTRY_CRON_MONITOR_SLUGS = 'outro-cron';
|
|
209
|
+
const prisma = fakePrisma();
|
|
210
|
+
const boom = new Error('cron falhou');
|
|
211
|
+
|
|
212
|
+
await expect(
|
|
213
|
+
runMonitoredCron(prisma, BASE, jest.fn().mockRejectedValue(boom)),
|
|
214
|
+
).resolves.toBeUndefined();
|
|
215
|
+
|
|
216
|
+
expect(captureException).toHaveBeenCalledWith(
|
|
217
|
+
boom,
|
|
218
|
+
expect.objectContaining({ mechanism: { handled: true, type: 'hedhog.cron' } }),
|
|
219
|
+
);
|
|
220
|
+
expect(prisma.$executeRaw).toHaveBeenCalledTimes(1);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('sem a env, o default deixa passar só o sac-mailbox-poller', async () => {
|
|
224
|
+
delete process.env.SENTRY_CRON_MONITOR_SLUGS;
|
|
225
|
+
const work = jest.fn().mockResolvedValue(undefined);
|
|
226
|
+
|
|
227
|
+
await runMonitoredCron(fakePrisma(), { ...BASE, slug: 'sac-mailbox-poller' }, work);
|
|
228
|
+
expect(withMonitor).toHaveBeenCalledTimes(1);
|
|
229
|
+
|
|
230
|
+
await runMonitoredCron(fakePrisma(), BASE, work);
|
|
231
|
+
expect(withMonitor).toHaveBeenCalledTimes(1);
|
|
232
|
+
expect(work).toHaveBeenCalledTimes(2);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
102
235
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as Sentry from '@sentry/nestjs';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
2
3
|
|
|
3
4
|
export type MonitoredCronOptions = {
|
|
4
5
|
/** Slug do monitor no Sentry (Crons). */
|
|
@@ -9,34 +10,103 @@ export type MonitoredCronOptions = {
|
|
|
9
10
|
checkinMarginMinutes?: number;
|
|
10
11
|
};
|
|
11
12
|
|
|
12
|
-
/** Fallback só para dimensionar
|
|
13
|
+
/** Fallback só para dimensionar a validade da lease (ver abaixo) quando um
|
|
13
14
|
* chamador futuro omitir `maxRuntimeMinutes` — os 5 crons atuais sempre informam. */
|
|
14
15
|
const DEFAULT_MAX_RUNTIME_MINUTES = 5;
|
|
15
16
|
|
|
16
|
-
/** Folga sobre `maxRuntimeMinutes`
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
const
|
|
17
|
+
/** Folga sobre `maxRuntimeMinutes` na validade da lease: cobre a diferença entre o
|
|
18
|
+
* que o Sentry tolera (`maxRuntime`/`checkinMargin`, que só marcam o check-in como
|
|
19
|
+
* perdido) e o instante a partir do qual outro pod pode assumir o tick. */
|
|
20
|
+
const LEASE_SLACK_SECONDS = 30;
|
|
20
21
|
|
|
21
|
-
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Slugs cujo check-in de Cron Monitoring é de fato emitido, em CSV. `*` libera
|
|
24
|
+
* todos.
|
|
25
|
+
*
|
|
26
|
+
* O plano do Sentry inclui UM monitor de cron e este projeto tem cinco: os quatro
|
|
27
|
+
* que sobram tinham o check-in descartado na entrada por cota ("Usage Exceeded"),
|
|
28
|
+
* e monitor que não recebe check-in não alarma — só marca "missed" para sempre no
|
|
29
|
+
* painel, que é pior do que não existir.
|
|
30
|
+
*
|
|
31
|
+
* A vaga fica com o cron de maior cadência (1 min), porque é o que detecta mais
|
|
32
|
+
* rápido a falha que SÓ o Cron Monitoring pega: o agendador parado em todos os
|
|
33
|
+
* pods. Falha com exceção dos outros quatro continua chegando pela captura no
|
|
34
|
+
* `catch` abaixo, com a tag `cron`.
|
|
35
|
+
*/
|
|
36
|
+
const DEFAULT_CRON_MONITOR_SLUGS = 'sac-mailbox-poller';
|
|
37
|
+
|
|
38
|
+
/** Cache pelo valor bruto: a env não muda em runtime, mas o teste troca. */
|
|
39
|
+
let parsedSlugsRaw: string | undefined;
|
|
40
|
+
let parsedSlugs: Set<string> = new Set();
|
|
41
|
+
|
|
42
|
+
function emitsCheckIn(slug: string): boolean {
|
|
43
|
+
const raw = process.env.SENTRY_CRON_MONITOR_SLUGS ?? DEFAULT_CRON_MONITOR_SLUGS;
|
|
44
|
+
|
|
45
|
+
if (raw !== parsedSlugsRaw) {
|
|
46
|
+
parsedSlugsRaw = raw;
|
|
47
|
+
parsedSlugs = new Set(
|
|
48
|
+
raw
|
|
49
|
+
.split(',')
|
|
50
|
+
.map((entry) => entry.trim())
|
|
51
|
+
.filter(Boolean),
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return parsedSlugs.has('*') || parsedSlugs.has(slug);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Identidade DESTA execução, não do pod: dois ticks seguidos no mesmo processo
|
|
60
|
+
* precisam de holders diferentes, senão um release atrasado do tick N apagaria a
|
|
61
|
+
* lease que o tick N+1 acabou de tomar.
|
|
62
|
+
*/
|
|
63
|
+
function newHolder(): string {
|
|
64
|
+
return `${process.env.HOSTNAME ?? 'local'}:${randomUUID()}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Tenta ganhar o tick com uma ÚNICA statement: quem consegue gravar
|
|
69
|
+
* `locked_until` no futuro venceu, os demais não recebem linha nenhuma de volta.
|
|
70
|
+
* Concorrentes serializam no índice único de `slug` — o perdedor reavalia o
|
|
71
|
+
* `WHERE` já com a linha do vencedor, então nunca há dois vencedores.
|
|
72
|
+
*
|
|
73
|
+
* O relógio de referência é sempre o `now()` do Postgres, dos dois lados: nenhum
|
|
74
|
+
* instante é calculado em JS, então divergência de relógio entre pods não conta.
|
|
75
|
+
*/
|
|
76
|
+
async function claimCronTick(
|
|
77
|
+
prisma: any,
|
|
78
|
+
slug: string,
|
|
79
|
+
holder: string,
|
|
80
|
+
leaseSeconds: number,
|
|
81
|
+
): Promise<boolean> {
|
|
82
|
+
const rows: unknown[] = await prisma.$queryRaw`
|
|
83
|
+
INSERT INTO "cron_lock" ("slug", "holder", "locked_until")
|
|
84
|
+
VALUES (${slug}, ${holder}, now() + make_interval(secs => ${leaseSeconds}::double precision))
|
|
85
|
+
ON CONFLICT ("slug") DO UPDATE
|
|
86
|
+
SET "holder" = EXCLUDED."holder",
|
|
87
|
+
"locked_until" = EXCLUDED."locked_until"
|
|
88
|
+
WHERE "cron_lock"."locked_until" <= now()
|
|
89
|
+
RETURNING 1 AS won
|
|
90
|
+
`;
|
|
91
|
+
|
|
92
|
+
return rows.length > 0;
|
|
93
|
+
}
|
|
26
94
|
|
|
27
95
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* garante a exclusão mútua entre pods.
|
|
96
|
+
* Devolve o tick antes do fim da lease. Sem isso o `sac-mailbox-poller` (que roda
|
|
97
|
+
* a cada minuto com lease de 2min30) só voltaria a rodar de 3 em 3 minutos.
|
|
98
|
+
*
|
|
99
|
+
* O `holder` no WHERE é o que impede liberar a lease de outra execução: se esta
|
|
100
|
+
* aqui estourou o prazo e outro pod já assumiu, o UPDATE não casa e não faz nada.
|
|
34
101
|
*/
|
|
35
|
-
async function
|
|
36
|
-
|
|
37
|
-
|
|
102
|
+
async function releaseCronTick(prisma: any, slug: string, holder: string): Promise<void> {
|
|
103
|
+
await prisma.$executeRaw`
|
|
104
|
+
UPDATE "cron_lock"
|
|
105
|
+
SET "locked_until" = now()
|
|
106
|
+
WHERE "slug" = ${slug}
|
|
107
|
+
AND "holder" = ${holder}
|
|
108
|
+
AND "locked_until" > now()
|
|
38
109
|
`;
|
|
39
|
-
return rows[0]?.locked === true;
|
|
40
110
|
}
|
|
41
111
|
|
|
42
112
|
/**
|
|
@@ -46,61 +116,83 @@ async function claimCronTick(tx: any, slug: string): Promise<boolean> {
|
|
|
46
116
|
* simultaneamente, o Sentry recebe 2×N check-ins por execução e estoura o
|
|
47
117
|
* rate limit de 6/min por monitor (ver docs/sentry.md).
|
|
48
118
|
*
|
|
49
|
-
* A
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* sac-sla-sweep.
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
119
|
+
* A exclusão mútua é uma LEASE na tabela `cron_lock`, não um lock de banco.
|
|
120
|
+
* Já passamos por duas versões piores:
|
|
121
|
+
*
|
|
122
|
+
* 1. `pg_try_advisory_xact_lock` numa transação que comitava logo em seguida: o
|
|
123
|
+
* Postgres liberava o lock no instante em que a query retornava, antes do
|
|
124
|
+
* trabalho real começar, e pods próximos no tempo (o que é comum, já que o
|
|
125
|
+
* @Cron dispara no mesmo segundo em todos) "ganhavam" o mesmo tick — foi o
|
|
126
|
+
* que gerou o incidente do sac-sla-sweep.
|
|
127
|
+
* 2. O mesmo lock, mas com a transação segurada durante `work()` inteiro. A
|
|
128
|
+
* exclusão passou a valer, ao custo de manter uma conexão do pool em
|
|
129
|
+
* `idle in transaction` por até 10 minutos. Com PRISMA_CONNECTION_LIMIT=5 por
|
|
130
|
+
* pod e cinco crons disputando (um deles de minuto em minuto), o pool
|
|
131
|
+
* esgotava e o próprio cron morria em "Unable to start a transaction in the
|
|
132
|
+
* given time" — o mecanismo de observabilidade consumindo o pool de que ele
|
|
133
|
+
* precisava (Sentry API-1H).
|
|
134
|
+
*
|
|
135
|
+
* A lease resolve as duas: a eleição é uma statement só (conexão devolvida na
|
|
136
|
+
* hora, `work()` roda sem segurar nada) e a validade gravada em `locked_until`
|
|
137
|
+
* é o que mantém os outros pods fora até o dono terminar ou o prazo vencer.
|
|
138
|
+
*
|
|
139
|
+
* Trade-off assumido: se `work()` passar de `maxRuntimeMinutes` + 30s, a lease
|
|
140
|
+
* vence e outro pod pode iniciar uma execução concorrente. A versão anterior
|
|
141
|
+
* abortava a transação no timeout — nenhum dos dois é bom, mas este não leva o
|
|
142
|
+
* pool junto.
|
|
61
143
|
*
|
|
62
144
|
* Quando outro pod já ganhou o tick, resolve para `undefined` sem chamar
|
|
63
145
|
* `work` — não é uma falha, é a mesma semântica de "outro worker já pegou o
|
|
64
146
|
* job" que a fila customizada já usa.
|
|
147
|
+
*
|
|
148
|
+
* O check-in só é emitido para os slugs de `SENTRY_CRON_MONITOR_SLUGS` (ver
|
|
149
|
+
* `DEFAULT_CRON_MONITOR_SLUGS`): a cota do plano não cobre os cinco monitores, e
|
|
150
|
+
* check-in sem cota é descartado na entrada. Tudo o mais — lease, eleição, span,
|
|
151
|
+
* captura de exceção — é igual para todos os slugs.
|
|
65
152
|
*/
|
|
66
153
|
export async function runMonitoredCron<T>(
|
|
67
154
|
prisma: any,
|
|
68
155
|
options: MonitoredCronOptions,
|
|
69
156
|
work: () => Promise<T>,
|
|
70
157
|
): Promise<T | undefined> {
|
|
71
|
-
const
|
|
72
|
-
(options.maxRuntimeMinutes ?? DEFAULT_MAX_RUNTIME_MINUTES) *
|
|
73
|
-
|
|
158
|
+
const leaseSeconds =
|
|
159
|
+
(options.maxRuntimeMinutes ?? DEFAULT_MAX_RUNTIME_MINUTES) * 60 + LEASE_SLACK_SECONDS;
|
|
160
|
+
const holder = newHolder();
|
|
74
161
|
|
|
75
162
|
try {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
163
|
+
if (!(await claimCronTick(prisma, options.slug, holder, leaseSeconds))) return undefined;
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const runTraced = () => Sentry.startSpan({ name: options.slug, op: 'cron.hedhog' }, work);
|
|
167
|
+
|
|
168
|
+
// Sem cota de monitor, o check-in seria descartado na entrada — o trabalho,
|
|
169
|
+
// o span e o relato de erro continuam idênticos. `startNewTrace` faz aqui o
|
|
170
|
+
// que o `isolateTrace` do withMonitor faz no outro ramo.
|
|
171
|
+
if (!emitsCheckIn(options.slug)) {
|
|
172
|
+
return await Sentry.startNewTrace(runTraced);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return await Sentry.withMonitor(options.slug, runTraced, {
|
|
176
|
+
schedule: { type: 'crontab', value: options.schedule },
|
|
177
|
+
timezone: 'America/Sao_Paulo',
|
|
178
|
+
...(options.maxRuntimeMinutes ? { maxRuntime: options.maxRuntimeMinutes } : {}),
|
|
179
|
+
...(options.checkinMarginMinutes ? { checkinMargin: options.checkinMarginMinutes } : {}),
|
|
180
|
+
// Distingue cada execução do cron como um trace próprio, em vez de
|
|
181
|
+
// herdar/continuar um trace pendurado do ciclo de poll anterior.
|
|
182
|
+
isolateTrace: true,
|
|
183
|
+
});
|
|
184
|
+
} finally {
|
|
185
|
+
// Best-effort: uma falha ao devolver a lease não pode virar o erro do cron
|
|
186
|
+
// (o trabalho já foi feito) e nem mascarar o erro real de `work()`. No pior
|
|
187
|
+
// caso a lease expira sozinha em `leaseSeconds`.
|
|
188
|
+
await releaseCronTick(prisma, options.slug, holder).catch(() => undefined);
|
|
189
|
+
}
|
|
96
190
|
} catch (error) {
|
|
97
|
-
// withMonitor já registrou o check-in como 'error' antes de relançar
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
// onTick, e o onUnhandledRejectionIntegration({mode:'strict'}) do
|
|
103
|
-
// instrument.ts derrubaria o processo por um erro que já está capturado.
|
|
191
|
+
// withMonitor já registrou o check-in como 'error' antes de relançar. O @Cron
|
|
192
|
+
// do chamador NUNCA deve rejeitar a partir daqui: a lib `cron` por baixo do
|
|
193
|
+
// @nestjs/schedule não garante tratamento de uma rejeição vinda do onTick, e
|
|
194
|
+
// o onUnhandledRejectionIntegration({mode:'strict'}) do instrument.ts
|
|
195
|
+
// derrubaria o processo por um erro que já está capturado.
|
|
104
196
|
Sentry.withScope((scope) => {
|
|
105
197
|
scope.setTag('cron', options.slug);
|
|
106
198
|
Sentry.captureException(error, { mechanism: { handled: true, type: 'hedhog.cron' } });
|
|
@@ -1,16 +1,34 @@
|
|
|
1
1
|
import { getLocaleText } from '@hed-hog/api-locale';
|
|
2
|
-
import { IsString
|
|
2
|
+
import { IsString } from 'class-validator';
|
|
3
|
+
import { getLocaleFromContext } from '../../utils/locale-context';
|
|
3
4
|
import { IsStrongPasswordWithSettings } from '../../validators/is-strong-password-with-settings.validator';
|
|
4
5
|
|
|
5
6
|
export class ChangePasswordDto {
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
// Só precisa ser texto — é comparada ao hash já salvo, não precisa atender à
|
|
8
|
+
// política de senha ATUAL. `@IsStrongPasswordWithSettings` estava aqui por engano:
|
|
9
|
+
// qualquer conta com senha anterior a um endurecimento de política (mais símbolos
|
|
10
|
+
// exigidos, por exemplo) ficaria travada pra sempre, sem conseguir nunca mais trocar
|
|
11
|
+
// a própria senha — a política tem que valer pra senha NOVA, não pra que já existe.
|
|
12
|
+
//
|
|
13
|
+
// `message` usa `getLocaleFromContext()` (o mesmo AsyncLocalStorage que
|
|
14
|
+
// `IsStrongPasswordWithSettingsConstraint.defaultMessage()` lê) porque
|
|
15
|
+
// `ValidationArguments` não carrega o locale da requisição — só o valor do campo.
|
|
16
|
+
@IsString({
|
|
17
|
+
message: () =>
|
|
18
|
+
getLocaleText(
|
|
19
|
+
'profile.changePassword.currentPasswordIsString',
|
|
20
|
+
getLocaleFromContext(),
|
|
21
|
+
'Current password must be a string.',
|
|
22
|
+
),
|
|
8
23
|
})
|
|
9
24
|
currentPassword: string;
|
|
10
25
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
26
|
+
// Antes só tinha `@IsString` + `MinLength(6)`: a nova senha não passava por
|
|
27
|
+
// NENHUMA regra de política (maiúscula, símbolo, senha vazada...) além do
|
|
28
|
+
// tamanho mínimo fixo. `IsStrongPasswordWithSettingsConstraint` já produz uma
|
|
29
|
+
// mensagem específica por regra via `defaultMessage()` (usa o mesmo
|
|
30
|
+
// `getLocaleFromContext()` acima), então não sobrescrevemos com `message` aqui —
|
|
31
|
+
// mesmo padrão de `CreateWithEmailAndPasswordDTO.password`.
|
|
32
|
+
@IsStrongPasswordWithSettings()
|
|
15
33
|
newPassword: string;
|
|
16
34
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach, jest } from '@jest/globals';
|
|
2
|
+
import { NotFoundException } from '@nestjs/common';
|
|
3
|
+
|
|
4
|
+
import { ProfileService } from './profile.service';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* O que estes casos protegem é uma frase só: "esta conta tem senha" precisa
|
|
8
|
+
* significar a mesma coisa aqui e no resto do core.
|
|
9
|
+
*
|
|
10
|
+
* `hasPasswordCredential` (usado pelo "esqueci minha senha") sempre exigiu
|
|
11
|
+
* credencial **ativa**, e a revogação é o que o convite de acesso enterprise usa
|
|
12
|
+
* para trocar "autorize o acesso" por "crie sua conta e defina a senha". Estas
|
|
13
|
+
* duas leituras do perfil ignoravam `revoked_at`: `changePassword` aceitava a
|
|
14
|
+
* senha de uma credencial revogada e regravava o hash nela, e o `hasPassword` do
|
|
15
|
+
* `GET /profile` — que o app usa para decidir se pede senha — dizia que sim.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const makeDeps = () => ({
|
|
19
|
+
prisma: {
|
|
20
|
+
user: { findUnique: jest.fn() },
|
|
21
|
+
user_credential: { findFirst: jest.fn(), update: jest.fn() },
|
|
22
|
+
},
|
|
23
|
+
user: { registerUserActivity: jest.fn() },
|
|
24
|
+
jwt: {},
|
|
25
|
+
setting: {},
|
|
26
|
+
security: { validatePassword: jest.fn(), hashArgon2: jest.fn() },
|
|
27
|
+
challenge: { resolveAppKeyFromOrigin: jest.fn() },
|
|
28
|
+
token: {},
|
|
29
|
+
mail: { resolveRecipientLocale: jest.fn(), sendTemplatedMail: jest.fn() },
|
|
30
|
+
eventEmitter: { emit: jest.fn() },
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const makeService = (deps = makeDeps()) => ({
|
|
34
|
+
service: new ProfileService(
|
|
35
|
+
deps.prisma as any,
|
|
36
|
+
deps.user as any,
|
|
37
|
+
deps.jwt as any,
|
|
38
|
+
deps.setting as any,
|
|
39
|
+
deps.security as any,
|
|
40
|
+
deps.challenge as any,
|
|
41
|
+
deps.token as any,
|
|
42
|
+
deps.mail as any,
|
|
43
|
+
deps.eventEmitter as any,
|
|
44
|
+
),
|
|
45
|
+
deps,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('ProfileService', () => {
|
|
49
|
+
afterEach(() => jest.clearAllMocks());
|
|
50
|
+
|
|
51
|
+
describe('changePassword', () => {
|
|
52
|
+
it('procura apenas credencial de senha não revogada', async () => {
|
|
53
|
+
const { service, deps } = makeService();
|
|
54
|
+
deps.prisma.user_credential.findFirst.mockResolvedValue(null);
|
|
55
|
+
|
|
56
|
+
await expect(
|
|
57
|
+
service.changePassword('pt-BR', 7, {
|
|
58
|
+
currentPassword: 'antiga',
|
|
59
|
+
newPassword: 'nova-senha-forte',
|
|
60
|
+
} as any),
|
|
61
|
+
).rejects.toThrow(NotFoundException);
|
|
62
|
+
|
|
63
|
+
expect(deps.prisma.user_credential.findFirst).toHaveBeenCalledWith({
|
|
64
|
+
where: { user_id: 7, type: 'password', revoked_at: null },
|
|
65
|
+
});
|
|
66
|
+
// Nada de validar hash revogado: sem credencial ativa o fluxo para antes.
|
|
67
|
+
expect(deps.security.validatePassword).not.toHaveBeenCalled();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('getProfile', () => {
|
|
72
|
+
it('conta apenas credencial de senha não revogada em hasPassword', async () => {
|
|
73
|
+
const { service, deps } = makeService();
|
|
74
|
+
deps.prisma.user.findUnique.mockResolvedValue({ id: 7, name: 'Aluno' });
|
|
75
|
+
deps.prisma.user_credential.findFirst.mockResolvedValue(null);
|
|
76
|
+
const count = jest.fn().mockResolvedValue(0);
|
|
77
|
+
(deps.prisma.user_credential as any).count = count;
|
|
78
|
+
|
|
79
|
+
const result = await service.getProfile(7);
|
|
80
|
+
|
|
81
|
+
expect(count).toHaveBeenCalledWith({
|
|
82
|
+
where: { user_id: 7, type: 'password', revoked_at: null },
|
|
83
|
+
});
|
|
84
|
+
expect(result).toEqual({ id: 7, name: 'Aluno', hasPassword: false });
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('devolve hasPassword true quando existe credencial ativa', async () => {
|
|
88
|
+
const { service, deps } = makeService();
|
|
89
|
+
deps.prisma.user.findUnique.mockResolvedValue({ id: 7, name: 'Aluno' });
|
|
90
|
+
(deps.prisma.user_credential as any).count = jest.fn().mockResolvedValue(1);
|
|
91
|
+
|
|
92
|
+
await expect(service.getProfile(7)).resolves.toMatchObject({
|
|
93
|
+
hasPassword: true,
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
});
|