@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
package/src/file/file.service.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
GetBucketNotificationConfigurationCommand,
|
|
3
|
+
GetObjectCommand,
|
|
3
4
|
HeadBucketCommand,
|
|
4
5
|
HeadObjectCommand,
|
|
5
6
|
PutBucketNotificationConfigurationCommand,
|
|
6
7
|
S3Client,
|
|
7
8
|
} from '@aws-sdk/client-s3';
|
|
9
|
+
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
8
10
|
import {
|
|
9
11
|
AddPermissionCommand,
|
|
10
12
|
CreateFunctionCommand,
|
|
@@ -23,6 +25,7 @@ import { PaginationDTO, PaginationService } from '@hed-hog/api-pagination';
|
|
|
23
25
|
import { PrismaService } from '@hed-hog/api-prisma';
|
|
24
26
|
import {
|
|
25
27
|
BadRequestException,
|
|
28
|
+
ForbiddenException,
|
|
26
29
|
forwardRef,
|
|
27
30
|
Inject,
|
|
28
31
|
Injectable,
|
|
@@ -31,6 +34,7 @@ import {
|
|
|
31
34
|
OnModuleInit
|
|
32
35
|
} from '@nestjs/common';
|
|
33
36
|
import { JwtService } from '@nestjs/jwt';
|
|
37
|
+
import { createHash } from 'crypto';
|
|
34
38
|
import type { Request } from 'express';
|
|
35
39
|
import { createWriteStream, promises as fs } from 'fs';
|
|
36
40
|
import { tmpdir } from 'os';
|
|
@@ -44,6 +48,8 @@ import {
|
|
|
44
48
|
} from '../integration-profile/integration-profile.utils';
|
|
45
49
|
import { SettingService } from '../setting/setting.service';
|
|
46
50
|
import { DeleteDTO } from './dto/delete.dto';
|
|
51
|
+
import { fixMojibake } from './filename-encoding.util';
|
|
52
|
+
import { MIMETYPE_SNIFF_BYTES } from './mimetype-sniff.util';
|
|
47
53
|
import {
|
|
48
54
|
ImageOptimizationService,
|
|
49
55
|
ImageVariantFormat,
|
|
@@ -96,21 +102,13 @@ export class FileService implements OnModuleInit {
|
|
|
96
102
|
return `${file.location}/${file.path}`;
|
|
97
103
|
}
|
|
98
104
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Nome que chegou com o encoding trocado (busboy entrega `originalname` em
|
|
107
|
+
* latin1, e a base ainda recebe nome importado com o mesmo defeito). A lógica
|
|
108
|
+
* mora no util porque a migration de backfill a reproduz em plpgsql.
|
|
109
|
+
*/
|
|
103
110
|
private normalizeUploadedFilename(filename: string): string {
|
|
104
|
-
|
|
105
|
-
return filename;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (!this.hasMojibake(filename)) {
|
|
109
|
-
return filename;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const decoded = Buffer.from(filename, 'latin1').toString('utf8');
|
|
113
|
-
return this.hasMojibake(decoded) ? filename : decoded;
|
|
111
|
+
return filename ? fixMojibake(filename) : filename;
|
|
114
112
|
}
|
|
115
113
|
|
|
116
114
|
constructor(
|
|
@@ -548,6 +546,9 @@ export class FileService implements OnModuleInit {
|
|
|
548
546
|
skipMimeValidation?: boolean;
|
|
549
547
|
skipSizeValidation?: boolean;
|
|
550
548
|
isPrivate?: boolean;
|
|
549
|
+
// Quem enviou. É o que permite recusar um `fileId` de outro usuário nas tools MCP
|
|
550
|
+
// (ver `assertFileUsable`); fica nulo nos caminhos internos sem usuário.
|
|
551
|
+
createdByUserId?: number | null;
|
|
551
552
|
},
|
|
552
553
|
) {
|
|
553
554
|
|
|
@@ -610,6 +611,7 @@ export class FileService implements OnModuleInit {
|
|
|
610
611
|
mimetype_id: await this.getMimeType(optimized.mimetype),
|
|
611
612
|
size: optimized.buffer.length,
|
|
612
613
|
is_private: options?.isPrivate ?? false,
|
|
614
|
+
created_by_user_id: options?.createdByUserId ?? null,
|
|
613
615
|
},
|
|
614
616
|
});
|
|
615
617
|
|
|
@@ -629,6 +631,8 @@ export class FileService implements OnModuleInit {
|
|
|
629
631
|
skipSizeValidation?: boolean;
|
|
630
632
|
// Tira o arquivo das rotas que servem por id sem autenticação.
|
|
631
633
|
isPrivate?: boolean;
|
|
634
|
+
// Quem enviou. Ver a nota do mesmo campo em `upload`.
|
|
635
|
+
createdByUserId?: number | null;
|
|
632
636
|
// Mesmo par maxWidth/maxHeight que `upload` aceita — só tem efeito em image/*.
|
|
633
637
|
resize?: { maxWidth?: number; maxHeight?: number };
|
|
634
638
|
},
|
|
@@ -652,9 +656,18 @@ export class FileService implements OnModuleInit {
|
|
|
652
656
|
);
|
|
653
657
|
// Recupera o tipo real quando o caller não informou o mimetype (default antigo era
|
|
654
658
|
// application/octet-stream, que fazia imagens caírem fora da otimização e do serve).
|
|
659
|
+
// Diferente de `upload`, aqui só existe um caminho no disco — e ler o arquivo inteiro
|
|
660
|
+
// é justamente o que o branch de streaming abaixo evita para SCORM/vídeo de vários GB.
|
|
661
|
+
// Por isso o sniffing recebe só o cabeçalho, e apenas quando o caller não declarou um
|
|
662
|
+
// tipo concreto: sem isso, uma imagem enviada como application/octet-stream sem
|
|
663
|
+
// extensão no nome escapava da otimização e era gravada byte a byte.
|
|
664
|
+
const declaredMimetype = metadata?.mimetype;
|
|
665
|
+
const isGenericMimetype =
|
|
666
|
+
!declaredMimetype || declaredMimetype === 'application/octet-stream';
|
|
655
667
|
const mimetype = await this.resolveEffectiveMimetype(
|
|
656
|
-
|
|
668
|
+
declaredMimetype,
|
|
657
669
|
originalname,
|
|
670
|
+
isGenericMimetype ? await this.readFileHeader(filePath) : undefined,
|
|
658
671
|
);
|
|
659
672
|
|
|
660
673
|
if (!metadata?.skipMimeValidation && !(await this.acceptMimetypes(mimetype))) {
|
|
@@ -687,6 +700,7 @@ export class FileService implements OnModuleInit {
|
|
|
687
700
|
mimetype_id: await this.getMimeType(mimetype),
|
|
688
701
|
size,
|
|
689
702
|
is_private: metadata?.isPrivate ?? false,
|
|
703
|
+
created_by_user_id: metadata?.createdByUserId ?? null,
|
|
690
704
|
},
|
|
691
705
|
});
|
|
692
706
|
}
|
|
@@ -724,20 +738,102 @@ export class FileService implements OnModuleInit {
|
|
|
724
738
|
mimetype_id: await this.getMimeType(optimized.mimetype),
|
|
725
739
|
size: optimized.buffer.length,
|
|
726
740
|
is_private: metadata?.isPrivate ?? false,
|
|
741
|
+
created_by_user_id: metadata?.createdByUserId ?? null,
|
|
727
742
|
},
|
|
728
743
|
});
|
|
729
744
|
|
|
730
745
|
return file;
|
|
731
746
|
}
|
|
732
747
|
|
|
748
|
+
/**
|
|
749
|
+
* Mimetypes de imagem que `optimizeBuffer` devolve intactos por decisão de projeto
|
|
750
|
+
* (vetor não vira bitmap, GIF animado não vira quadro único). Enfileirá-los para
|
|
751
|
+
* reotimização seria custo de download e reprocessamento com resultado garantidamente
|
|
752
|
+
* nulo, então ficam fora da varredura.
|
|
753
|
+
*/
|
|
754
|
+
private static readonly NON_OPTIMIZABLE_IMAGE_MIMETYPES = [
|
|
755
|
+
'image/svg+xml',
|
|
756
|
+
'image/gif',
|
|
757
|
+
];
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Lista as imagens armazenadas que valem uma passada por `reoptimizeFile`, para a
|
|
761
|
+
* operação em massa de reprocessamento do acervo.
|
|
762
|
+
*
|
|
763
|
+
* Devolve junto o total e a soma de bytes do filtro inteiro (não só da página), que é
|
|
764
|
+
* o que permite mostrar uma prévia antes de enfileirar dezenas de milhares de jobs.
|
|
765
|
+
* O core não conhece a fila — quem enfileira é a library `queue`, que depende deste
|
|
766
|
+
* pacote e não o contrário.
|
|
767
|
+
*/
|
|
768
|
+
async findOptimizableImages(params?: {
|
|
769
|
+
/** Piso de tamanho em bytes; abaixo disso o ganho não paga o reprocessamento. */
|
|
770
|
+
minSize?: number;
|
|
771
|
+
/** Restringe a `location` exatas (ex.: `lms/xp/skills`), para rollout em ondas. */
|
|
772
|
+
locations?: string[];
|
|
773
|
+
offset?: number;
|
|
774
|
+
limit?: number;
|
|
775
|
+
}): Promise<{ ids: number[]; total: number; totalBytes: number }> {
|
|
776
|
+
const minSize = Math.max(0, Math.trunc(Number(params?.minSize) || 0));
|
|
777
|
+
const locations = (params?.locations ?? [])
|
|
778
|
+
.map((location) => String(location).trim())
|
|
779
|
+
.filter(Boolean);
|
|
780
|
+
|
|
781
|
+
const where: any = {
|
|
782
|
+
size: { gte: minSize },
|
|
783
|
+
file_mimetype: {
|
|
784
|
+
name: {
|
|
785
|
+
startsWith: 'image/',
|
|
786
|
+
notIn: FileService.NON_OPTIMIZABLE_IMAGE_MIMETYPES,
|
|
787
|
+
},
|
|
788
|
+
},
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
if (locations.length) {
|
|
792
|
+
where.location = { in: locations };
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
const [aggregate, rows] = await Promise.all([
|
|
796
|
+
this.prismaService.file.aggregate({
|
|
797
|
+
where,
|
|
798
|
+
_count: { _all: true },
|
|
799
|
+
_sum: { size: true },
|
|
800
|
+
}),
|
|
801
|
+
this.prismaService.file.findMany({
|
|
802
|
+
where,
|
|
803
|
+
select: { id: true },
|
|
804
|
+
// Ordem estável para que o enfileiramento paginado não pule nem repita linhas.
|
|
805
|
+
orderBy: { id: 'asc' },
|
|
806
|
+
skip: Math.max(0, Math.trunc(Number(params?.offset) || 0)),
|
|
807
|
+
take: Math.min(5000, Math.max(1, Math.trunc(Number(params?.limit) || 1000))),
|
|
808
|
+
}),
|
|
809
|
+
]);
|
|
810
|
+
|
|
811
|
+
return {
|
|
812
|
+
ids: rows.map((row) => row.id),
|
|
813
|
+
total: aggregate._count._all ?? 0,
|
|
814
|
+
totalBytes: Number(aggregate._sum.size ?? 0),
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* Ganho mínimo para valer a regravação de um arquivo que já está no formato final.
|
|
820
|
+
*
|
|
821
|
+
* Recomprimir WebP sobre WebP sempre devolve alguns bytes a menos, então um limiar de
|
|
822
|
+
* "estritamente menor" nunca converge: cada passada da varredura trocaria o arquivo
|
|
823
|
+
* por um ganho irrisório e mais uma geração de perda de qualidade. Medido aqui, três
|
|
824
|
+
* passadas seguidas rendiam 10926 → 10664 → 10662 bytes — a última custou uma
|
|
825
|
+
* recompressão inteira para economizar 2 bytes.
|
|
826
|
+
*/
|
|
827
|
+
private static readonly REOPTIMIZE_MIN_GAIN_RATIO = 0.1;
|
|
828
|
+
|
|
733
829
|
/**
|
|
734
830
|
* Re-applies the current image-optimization settings to an already-stored file,
|
|
735
831
|
* rewriting it in place: the `file` record keeps the same id, so every row that
|
|
736
832
|
* references it (course_image, course_lesson_video_frame, ...) stays valid without
|
|
737
|
-
* any FK juggling. Only replaces the stored object when the
|
|
738
|
-
*
|
|
739
|
-
* operation idempotent and avoids repeated
|
|
740
|
-
* left untouched.
|
|
833
|
+
* any FK juggling. Only replaces the stored object when the mimetype changed
|
|
834
|
+
* (e.g. PNG → WebP) or the result is smaller by a margin that justifies the rewrite —
|
|
835
|
+
* that margin is what actually makes the operation idempotent and avoids repeated
|
|
836
|
+
* lossy recompression. Non-image files are left untouched.
|
|
741
837
|
*/
|
|
742
838
|
async reoptimizeFile(fileId: number): Promise<{
|
|
743
839
|
changed: boolean;
|
|
@@ -760,9 +856,15 @@ export class FileService implements OnModuleInit {
|
|
|
760
856
|
);
|
|
761
857
|
|
|
762
858
|
const mimetypeChanged = optimized.mimetype !== currentMimetype;
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
859
|
+
// Mudança de formato é o ganho principal (PNG → WebP) e vale por si; sem ela, só
|
|
860
|
+
// regrava quando a economia passa do limiar. `optimizeBuffer` já garante que o
|
|
861
|
+
// resultado nunca é maior que a entrada neste caminho (sem crop de alvo exato).
|
|
862
|
+
const worthRewriting =
|
|
863
|
+
oldSize > 0 &&
|
|
864
|
+
(oldSize - optimized.buffer.length) / oldSize >=
|
|
865
|
+
FileService.REOPTIMIZE_MIN_GAIN_RATIO;
|
|
866
|
+
|
|
867
|
+
if (!mimetypeChanged && !worthRewriting) {
|
|
766
868
|
return {
|
|
767
869
|
changed: false,
|
|
768
870
|
oldSize,
|
|
@@ -850,7 +952,130 @@ export class FileService implements OnModuleInit {
|
|
|
850
952
|
return files.map((file) => file.id);
|
|
851
953
|
}
|
|
852
954
|
|
|
853
|
-
|
|
955
|
+
/**
|
|
956
|
+
* `ownerUserId` restringe a listagem aos arquivos enviados por aquele usuário.
|
|
957
|
+
*
|
|
958
|
+
* Existe para as tools MCP: sem ele, `core.files.list` é um índice de tudo o que
|
|
959
|
+
* qualquer empresa já subiu, incluindo o que está gravado como `is_private`. O
|
|
960
|
+
* controller REST continua chamando sem o parâmetro, porque ali a rota já é `admin`
|
|
961
|
+
* e a tela de arquivos é justamente a visão global.
|
|
962
|
+
*/
|
|
963
|
+
/**
|
|
964
|
+
* Grava um stream em disco medindo, hasheando e guardando o cabeçalho de uma vez só.
|
|
965
|
+
*
|
|
966
|
+
* O upload assinado recebe o binário cru no corpo do PUT, e nada garante que venha com
|
|
967
|
+
* `Content-Length` — sem o contador abaixo, um corpo aberto encheria o disco do pod. O
|
|
968
|
+
* corte é feito em pleno voo, antes de o excedente chegar ao arquivo.
|
|
969
|
+
*
|
|
970
|
+
* Passa o cabeçalho adiante porque conferir o tipo real é obrigatório nesse caminho, e
|
|
971
|
+
* reler o arquivo depois seria uma segunda passada por nada.
|
|
972
|
+
*/
|
|
973
|
+
async writeStreamToPath(
|
|
974
|
+
source: NodeJS.ReadableStream,
|
|
975
|
+
targetPath: string,
|
|
976
|
+
maxBytes: number,
|
|
977
|
+
): Promise<{ bytes: number; sha256: string; header: Buffer }> {
|
|
978
|
+
const hash = createHash('sha256');
|
|
979
|
+
const headerChunks: Buffer[] = [];
|
|
980
|
+
let headerLength = 0;
|
|
981
|
+
let bytes = 0;
|
|
982
|
+
|
|
983
|
+
const target = createWriteStream(targetPath);
|
|
984
|
+
|
|
985
|
+
await pipeline(
|
|
986
|
+
source as any,
|
|
987
|
+
async function* (chunks: AsyncIterable<Buffer>) {
|
|
988
|
+
for await (const chunk of chunks) {
|
|
989
|
+
const buffer = Buffer.from(chunk);
|
|
990
|
+
bytes += buffer.length;
|
|
991
|
+
|
|
992
|
+
if (bytes > maxBytes) {
|
|
993
|
+
throw new BadRequestException(
|
|
994
|
+
`Upload exceeds the declared size of ${maxBytes} bytes`,
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
hash.update(buffer);
|
|
999
|
+
|
|
1000
|
+
if (headerLength < MIMETYPE_SNIFF_BYTES) {
|
|
1001
|
+
const slice = buffer.subarray(0, MIMETYPE_SNIFF_BYTES - headerLength);
|
|
1002
|
+
headerChunks.push(slice);
|
|
1003
|
+
headerLength += slice.length;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
yield buffer;
|
|
1007
|
+
}
|
|
1008
|
+
},
|
|
1009
|
+
target,
|
|
1010
|
+
);
|
|
1011
|
+
|
|
1012
|
+
return {
|
|
1013
|
+
bytes,
|
|
1014
|
+
sha256: hash.digest('hex'),
|
|
1015
|
+
header: Buffer.concat(headerChunks),
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/**
|
|
1020
|
+
* Conteúdo de um arquivo já armazenado, em memória.
|
|
1021
|
+
*
|
|
1022
|
+
* Existe para as tools que consomem os bytes sem persistir nada (importar extrato,
|
|
1023
|
+
* extrair dados de uma nota com IA): elas recebem um `fileId` e precisam do arquivo,
|
|
1024
|
+
* não de um stream. O teto evita que um `fileId` apontando para um SCORM de vários GB
|
|
1025
|
+
* derrube o pod — quem serve arquivo grande usa `openReadStream` e não passa por aqui.
|
|
1026
|
+
*/
|
|
1027
|
+
async bufferById(fileId: number, maxBytes: number): Promise<Buffer> {
|
|
1028
|
+
const { stream } = await this.openReadStreamById(fileId);
|
|
1029
|
+
const chunks: Buffer[] = [];
|
|
1030
|
+
let total = 0;
|
|
1031
|
+
|
|
1032
|
+
for await (const chunk of stream as AsyncIterable<Buffer>) {
|
|
1033
|
+
total += chunk.length;
|
|
1034
|
+
|
|
1035
|
+
if (total > maxBytes) {
|
|
1036
|
+
(stream as any).destroy?.();
|
|
1037
|
+
throw new BadRequestException(
|
|
1038
|
+
`File ${fileId} is larger than the ${maxBytes} bytes this operation can load`,
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
chunks.push(Buffer.from(chunk));
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
return Buffer.concat(chunks);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
/**
|
|
1049
|
+
* Nome do mimetype de um arquivo.
|
|
1050
|
+
*
|
|
1051
|
+
* `get()` e `upload()` devolvem a linha crua, sem a relação, e cada chamador que
|
|
1052
|
+
* precisava do tipo vinha refazendo o `include` por conta. Como o cache de
|
|
1053
|
+
* `getMimeType` é id-por-nome, este é o inverso dele e sai do mesmo mapa quando já
|
|
1054
|
+
* tiver sido consultado.
|
|
1055
|
+
*/
|
|
1056
|
+
async mimetypeNameById(mimetypeId: number): Promise<string> {
|
|
1057
|
+
const cached = Object.entries(this.mimetypes).find(
|
|
1058
|
+
([, id]) => id === mimetypeId,
|
|
1059
|
+
);
|
|
1060
|
+
|
|
1061
|
+
if (cached) {
|
|
1062
|
+
return cached[0];
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
const row = await this.prismaService.file_mimetype.findUnique({
|
|
1066
|
+
where: { id: mimetypeId },
|
|
1067
|
+
select: { name: true },
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
if (row?.name) {
|
|
1071
|
+
this.mimetypes[row.name] = mimetypeId;
|
|
1072
|
+
return row.name;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
return 'application/octet-stream';
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
async getFiles(paginationParams: PaginationDTO, ownerUserId?: number) {
|
|
854
1079
|
const fields = ['filename', 'path'];
|
|
855
1080
|
const OR = this.prismaService.createInsensitiveSearch(
|
|
856
1081
|
fields,
|
|
@@ -863,11 +1088,84 @@ export class FileService implements OnModuleInit {
|
|
|
863
1088
|
{
|
|
864
1089
|
where: {
|
|
865
1090
|
OR,
|
|
1091
|
+
...(ownerUserId ? { created_by_user_id: ownerUserId } : {}),
|
|
866
1092
|
},
|
|
867
1093
|
},
|
|
868
1094
|
);
|
|
869
1095
|
}
|
|
870
1096
|
|
|
1097
|
+
/**
|
|
1098
|
+
* Papéis que enxergam qualquer arquivo. Os mesmos que já governam as rotas de arquivo
|
|
1099
|
+
* em `route.yaml` — quem administra o acervo pela tela também o administra pelo MCP.
|
|
1100
|
+
*/
|
|
1101
|
+
private static readonly FILE_ADMIN_ROLES = ['admin', 'admin-access'];
|
|
1102
|
+
|
|
1103
|
+
/** Cache curto de papéis, no mesmo espírito do McpPermissionService. */
|
|
1104
|
+
private readonly fileAdminCache = new Map<number, { value: boolean; at: number }>();
|
|
1105
|
+
private static readonly FILE_ADMIN_CACHE_TTL_MS = 30_000;
|
|
1106
|
+
|
|
1107
|
+
async isFileAdmin(userId: number): Promise<boolean> {
|
|
1108
|
+
const cached = this.fileAdminCache.get(userId);
|
|
1109
|
+
|
|
1110
|
+
if (cached && Date.now() - cached.at < FileService.FILE_ADMIN_CACHE_TTL_MS) {
|
|
1111
|
+
return cached.value;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
const found = await this.prismaService.role_user.findFirst({
|
|
1115
|
+
where: {
|
|
1116
|
+
user_id: userId,
|
|
1117
|
+
role: { slug: { in: FileService.FILE_ADMIN_ROLES } },
|
|
1118
|
+
},
|
|
1119
|
+
select: { role_id: true },
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
const value = Boolean(found);
|
|
1123
|
+
this.fileAdminCache.set(userId, { value, at: Date.now() });
|
|
1124
|
+
|
|
1125
|
+
return value;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
/**
|
|
1129
|
+
* Porteiro do `fileId` vindo de fora: só o dono usa um arquivo, e quem administra o
|
|
1130
|
+
* acervo usa qualquer um.
|
|
1131
|
+
*
|
|
1132
|
+
* Sem isto, apontar um `fileId` numa tool seria enumerar — id de arquivo é sequencial,
|
|
1133
|
+
* exatamente o motivo pelo qual `is_private` existe. Arquivo anterior a esta coluna
|
|
1134
|
+
* (ou gravado por cron/seed/webhook) fica com dono nulo e só o admin alcança: não dá
|
|
1135
|
+
* para provar de quem ele é, e adivinhar a favor do chamador seria abrir a porta que
|
|
1136
|
+
* este método fecha.
|
|
1137
|
+
*/
|
|
1138
|
+
async assertFileUsable(fileId: number, userId: number) {
|
|
1139
|
+
if (!Number.isInteger(fileId) || fileId <= 0) {
|
|
1140
|
+
throw new BadRequestException(`Invalid file id: ${fileId}`);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
const file = await this.prismaService.file.findUnique({
|
|
1144
|
+
where: { id: fileId },
|
|
1145
|
+
include: { file_mimetype: true },
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
// Mesma resposta para inexistente e para alheio seria o ideal contra enumeração, mas
|
|
1149
|
+
// `get()` já responde 404 com o id, e divergir aqui só confundiria quem integra.
|
|
1150
|
+
if (!file) {
|
|
1151
|
+
throw new NotFoundException(`File not found: ${fileId}`);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
if (file.created_by_user_id === userId) {
|
|
1155
|
+
return file;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
if (await this.isFileAdmin(userId)) {
|
|
1159
|
+
return file;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
throw new ForbiddenException(
|
|
1163
|
+
file.created_by_user_id === null
|
|
1164
|
+
? `File ${fileId} has no recorded owner and can only be used by an administrator`
|
|
1165
|
+
: `File ${fileId} belongs to another user`,
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
871
1169
|
async get(fileId: number) {
|
|
872
1170
|
const file = await this.prismaService.file.findUnique({
|
|
873
1171
|
where: { id: fileId },
|
|
@@ -1028,19 +1326,45 @@ export class FileService implements OnModuleInit {
|
|
|
1028
1326
|
return undefined;
|
|
1029
1327
|
}
|
|
1030
1328
|
|
|
1329
|
+
/**
|
|
1330
|
+
* Resolve o `file` cujo `resolveProviderPath` (location + path, ou path já combinado) bate com
|
|
1331
|
+
* o `filepath` embutido no token de download — é o inverso de `resolveProviderPath`.
|
|
1332
|
+
*
|
|
1333
|
+
* Bug corrigido (relatado como "Token de download inválido ou expirado" no storage local):
|
|
1334
|
+
* `tempURLById` assina o token com `resolveProviderPath(file)` (ex.:
|
|
1335
|
+
* "class-assignment-tasks/abc123.pdf", já com o prefixo de `location`), mas o upload local/S3
|
|
1336
|
+
* grava só o basename em `file.path` ("abc123.pdf") e o `location` separado em outra coluna.
|
|
1337
|
+
* Um `findFirst({ where: { path: filepath } })` direto nunca batia para esses arquivos — sempre
|
|
1338
|
+
* caía no `catch` abaixo e mostrava a mensagem genérica de token inválido, mesmo com o token
|
|
1339
|
+
* (e o arquivo) perfeitamente válidos. S3 mascarava isso porque `S3Provider.tempURL` devolve uma
|
|
1340
|
+
* URL assinada direto do bucket, sem passar por este método.
|
|
1341
|
+
*/
|
|
1342
|
+
private async findFileByResolvedPath(filepath: string) {
|
|
1343
|
+
const bySameValue = await this.prismaService.file.findFirst({
|
|
1344
|
+
where: { path: filepath },
|
|
1345
|
+
include: { file_mimetype: true },
|
|
1346
|
+
});
|
|
1347
|
+
if (bySameValue) return bySameValue;
|
|
1348
|
+
|
|
1349
|
+
const slashIndex = filepath.lastIndexOf('/');
|
|
1350
|
+
if (slashIndex === -1) return null;
|
|
1351
|
+
|
|
1352
|
+
return this.prismaService.file.findFirst({
|
|
1353
|
+
where: {
|
|
1354
|
+
location: filepath.slice(0, slashIndex),
|
|
1355
|
+
path: filepath.slice(slashIndex + 1),
|
|
1356
|
+
},
|
|
1357
|
+
include: { file_mimetype: true },
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1031
1361
|
async download(locale:string, token: string) {
|
|
1032
1362
|
try {
|
|
1033
1363
|
const { filepath } = this.jwtService.verify(token, {
|
|
1034
1364
|
secret: process.env.JWT_SECRET,
|
|
1035
1365
|
});
|
|
1036
1366
|
|
|
1037
|
-
|
|
1038
|
-
const file = await this.prismaService.file.findFirst({
|
|
1039
|
-
where: { path: filepath },
|
|
1040
|
-
include: {
|
|
1041
|
-
file_mimetype: true,
|
|
1042
|
-
},
|
|
1043
|
-
});
|
|
1367
|
+
const file = await this.findFileByResolvedPath(filepath);
|
|
1044
1368
|
|
|
1045
1369
|
if (!file) {
|
|
1046
1370
|
throw new NotFoundException(getLocaleText('file.download.not_found', locale, `Not found: ${filepath}`));
|
|
@@ -1186,6 +1510,30 @@ export class FileService implements OnModuleInit {
|
|
|
1186
1510
|
return map[ext] ?? null;
|
|
1187
1511
|
}
|
|
1188
1512
|
|
|
1513
|
+
/**
|
|
1514
|
+
* Lê só o começo do arquivo, o bastante para o sharp reconhecer o formato pelo
|
|
1515
|
+
* cabeçalho. Existe para o sniffing de `uploadFromPath`, que não pode chamar
|
|
1516
|
+
* `fs.readFile`: o mesmo caminho recebe pacotes SCORM e vídeos de vários GB, e
|
|
1517
|
+
* carregá-los inteiros estoura o limite de Buffer. Devolve `undefined` em qualquer
|
|
1518
|
+
* falha de leitura — quem chama já trata a ausência do buffer.
|
|
1519
|
+
*/
|
|
1520
|
+
private async readFileHeader(
|
|
1521
|
+
filePath: string,
|
|
1522
|
+
bytes = 64 * 1024,
|
|
1523
|
+
): Promise<Buffer | undefined> {
|
|
1524
|
+
let handle: fs.FileHandle | undefined;
|
|
1525
|
+
try {
|
|
1526
|
+
handle = await fs.open(filePath, 'r');
|
|
1527
|
+
const buffer = Buffer.alloc(bytes);
|
|
1528
|
+
const { bytesRead } = await handle.read(buffer, 0, bytes, 0);
|
|
1529
|
+
return bytesRead > 0 ? buffer.subarray(0, bytesRead) : undefined;
|
|
1530
|
+
} catch {
|
|
1531
|
+
return undefined;
|
|
1532
|
+
} finally {
|
|
1533
|
+
await handle?.close().catch(() => undefined);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1189
1537
|
/**
|
|
1190
1538
|
* Resolves the MIME type to persist for an upload. When the incoming mimetype is
|
|
1191
1539
|
* generic (application/octet-stream or empty), recovers the real image type from
|
|
@@ -1197,29 +1545,7 @@ export class FileService implements OnModuleInit {
|
|
|
1197
1545
|
name: string,
|
|
1198
1546
|
buffer?: Buffer,
|
|
1199
1547
|
): Promise<string> {
|
|
1200
|
-
if (mimetype && mimetype.startsWith('image/'))
|
|
1201
|
-
// O mimetype que chega do navegador vem da EXTENSÃO do arquivo, não do
|
|
1202
|
-
// conteúdo: um PNG renomeado para `.svg` é anunciado como `image/svg+xml`.
|
|
1203
|
-
// Gravar isso faz o endpoint de imagem servir bytes PNG sob Content-Type
|
|
1204
|
-
// de SVG e, com o `nosniff` que a API envia, o navegador não corrige
|
|
1205
|
-
// sozinho — a imagem simplesmente não renderiza, sem erro nenhum.
|
|
1206
|
-
// Com os bytes em mãos, quem decide é a assinatura do arquivo.
|
|
1207
|
-
if (buffer) {
|
|
1208
|
-
const sniffed =
|
|
1209
|
-
await this.imageOptimizationService.detectImageMimetype(buffer);
|
|
1210
|
-
// Só sobrepõe quando o sniffing reconheceu o formato: um `null` significa
|
|
1211
|
-
// que o sharp não entendeu o buffer, e aí o declarado ainda é o melhor
|
|
1212
|
-
// palpite disponível.
|
|
1213
|
-
if (sniffed && sniffed !== mimetype) {
|
|
1214
|
-
this.logger.warn(
|
|
1215
|
-
`Upload "${name}" foi anunciado como ${mimetype}, mas os bytes são ${sniffed}. Persistindo o tipo real.`,
|
|
1216
|
-
);
|
|
1217
|
-
return sniffed;
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
return mimetype;
|
|
1222
|
-
}
|
|
1548
|
+
if (mimetype && mimetype.startsWith('image/')) return mimetype;
|
|
1223
1549
|
// Tipos concretos não-imagem (video, pdf, ...) são mantidos como estão.
|
|
1224
1550
|
if (mimetype && mimetype !== 'application/octet-stream') return mimetype;
|
|
1225
1551
|
|
|
@@ -1514,6 +1840,17 @@ export class FileService implements OnModuleInit {
|
|
|
1514
1840
|
return url;
|
|
1515
1841
|
}
|
|
1516
1842
|
|
|
1843
|
+
/** Par de `tempURLById`, pela mesma razão: `path` cru resolve para NoSuchKey no S3. */
|
|
1844
|
+
async tempOpenURLById(fileId: number, expires = 3600) {
|
|
1845
|
+
const url = await this.tempURLById(fileId, expires);
|
|
1846
|
+
|
|
1847
|
+
if (typeof url === 'string') {
|
|
1848
|
+
return url.replace('/file/download/', '/file/open/');
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
return url;
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1517
1854
|
async getTemporaryCredentials(params: {
|
|
1518
1855
|
accessKeyId: string;
|
|
1519
1856
|
secretAccessKey: string;
|
|
@@ -1596,6 +1933,49 @@ export class FileService implements OnModuleInit {
|
|
|
1596
1933
|
};
|
|
1597
1934
|
}
|
|
1598
1935
|
|
|
1936
|
+
/**
|
|
1937
|
+
* URL assinada de um objeto em bucket avulso.
|
|
1938
|
+
*
|
|
1939
|
+
* O `getFileUrl` do provider resolve arquivos do acervo, que vivem no destino
|
|
1940
|
+
* ativo do core. Este metodo serve para os buckets que outras libraries
|
|
1941
|
+
* configuram por conta propria — o Studio grava no perfil de armazenamento
|
|
1942
|
+
* dele, que pode nao ser o mesmo — e por isso recebe as credenciais e o bucket
|
|
1943
|
+
* de fora, como o `headS3Object` ao lado.
|
|
1944
|
+
*/
|
|
1945
|
+
async signS3Object(params: {
|
|
1946
|
+
accessKeyId: string;
|
|
1947
|
+
secretAccessKey: string;
|
|
1948
|
+
sessionToken?: string;
|
|
1949
|
+
region: string;
|
|
1950
|
+
bucket: string;
|
|
1951
|
+
key: string;
|
|
1952
|
+
expiresInSeconds?: number;
|
|
1953
|
+
downloadFileName?: string;
|
|
1954
|
+
}): Promise<string> {
|
|
1955
|
+
const s3 = new S3Client({
|
|
1956
|
+
region: params.region,
|
|
1957
|
+
credentials: {
|
|
1958
|
+
accessKeyId: params.accessKeyId,
|
|
1959
|
+
secretAccessKey: params.secretAccessKey,
|
|
1960
|
+
...(params.sessionToken ? { sessionToken: params.sessionToken } : {}),
|
|
1961
|
+
},
|
|
1962
|
+
});
|
|
1963
|
+
|
|
1964
|
+
const command = new GetObjectCommand({
|
|
1965
|
+
Bucket: params.bucket,
|
|
1966
|
+
Key: params.key,
|
|
1967
|
+
...(params.downloadFileName
|
|
1968
|
+
? {
|
|
1969
|
+
ResponseContentDisposition: `attachment; filename="${params.downloadFileName.replace(/"/g, '')}"`,
|
|
1970
|
+
}
|
|
1971
|
+
: {}),
|
|
1972
|
+
});
|
|
1973
|
+
|
|
1974
|
+
return getSignedUrl(s3 as any, command, {
|
|
1975
|
+
expiresIn: params.expiresInSeconds ?? 900,
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1599
1979
|
async headS3Bucket(params: {
|
|
1600
1980
|
accessKeyId: string;
|
|
1601
1981
|
secretAccessKey: string;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals';
|
|
2
|
+
import { fixMojibake, hasMojibake } from './filename-encoding.util';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Inverso exato do que o util desfaz: escreve o texto em UTF-8 e lê os bytes
|
|
6
|
+
* como Latin-1. É como o nome se quebra de verdade, e aplicá-lo duas vezes
|
|
7
|
+
* reproduz a camada dupla que existe na base (file id 19768 em produção).
|
|
8
|
+
*/
|
|
9
|
+
const breakEncoding = (value: string): string =>
|
|
10
|
+
Buffer.from(value, 'utf8').toString('latin1');
|
|
11
|
+
|
|
12
|
+
describe('fixMojibake', () => {
|
|
13
|
+
it('desfaz uma camada', () => {
|
|
14
|
+
expect(fixMojibake('Introdução ao JavaScript.zip')).toBe(
|
|
15
|
+
'Introdução ao JavaScript.zip',
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('desfaz a camada dupla que veio da importação', () => {
|
|
20
|
+
const original = '04_Aula - Introdução ao JavaScript - Básico I.zip';
|
|
21
|
+
const broken = breakEncoding(breakEncoding(original));
|
|
22
|
+
|
|
23
|
+
expect(broken).not.toBe(original);
|
|
24
|
+
expect(fixMojibake(broken)).toBe(original);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('recupera caractere de três bytes, como o travessão', () => {
|
|
28
|
+
const original = 'Curso 1 — Unidade Biomassa Sul.pdf';
|
|
29
|
+
|
|
30
|
+
expect(fixMojibake(breakEncoding(original))).toBe(original);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('não mexe em nome já correto', () => {
|
|
34
|
+
expect(fixMojibake('Introdução ao JavaScript.zip')).toBe(
|
|
35
|
+
'Introdução ao JavaScript.zip',
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* O nome casa o padrão por causa do "ÃO", mas os bytes Latin-1 (C7 C3 4F) não
|
|
41
|
+
* formam UTF-8. Converter aqui destruiria um nome que está certo.
|
|
42
|
+
*/
|
|
43
|
+
it('preserva nome em caixa alta que casa o padrão sem ser mojibake', () => {
|
|
44
|
+
expect(hasMojibake('MANUTENÇÃO PREVENTIVA.pdf')).toBe(true);
|
|
45
|
+
expect(fixMojibake('MANUTENÇÃO PREVENTIVA.pdf')).toBe(
|
|
46
|
+
'MANUTENÇÃO PREVENTIVA.pdf',
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('preserva vazio e ASCII puro', () => {
|
|
51
|
+
expect(fixMojibake('')).toBe('');
|
|
52
|
+
expect(fixMojibake('relatorio-2026.pdf')).toBe('relatorio-2026.pdf');
|
|
53
|
+
});
|
|
54
|
+
});
|