@hed-hog/core 0.0.390 → 0.0.394
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/README.md +39 -0
- package/dist/access-log/access-log.controller.d.ts.map +1 -1
- package/dist/access-log/access-log.controller.js +1 -1
- package/dist/access-log/access-log.controller.js.map +1 -1
- package/dist/access-log/access-log.interceptor.d.ts.map +1 -1
- package/dist/access-log/access-log.interceptor.js +8 -5
- package/dist/access-log/access-log.interceptor.js.map +1 -1
- package/dist/access-log/access-log.service.d.ts +4 -0
- package/dist/access-log/access-log.service.d.ts.map +1 -1
- package/dist/access-log/access-log.service.js +31 -4
- package/dist/access-log/access-log.service.js.map +1 -1
- package/dist/access-log/dto/query-access-log.dto.d.ts +2 -0
- package/dist/access-log/dto/query-access-log.dto.d.ts.map +1 -1
- package/dist/access-log/dto/query-access-log.dto.js +5 -0
- package/dist/access-log/dto/query-access-log.dto.js.map +1 -1
- package/dist/ai/ai.controller.d.ts +12 -8
- package/dist/ai/ai.controller.d.ts.map +1 -1
- package/dist/ai/ai.controller.js +17 -2
- package/dist/ai/ai.controller.js.map +1 -1
- package/dist/ai/ai.module.d.ts.map +1 -1
- package/dist/ai/ai.module.js +3 -2
- package/dist/ai/ai.module.js.map +1 -1
- package/dist/ai/ai.service.d.ts +30 -1
- package/dist/ai/ai.service.d.ts.map +1 -1
- package/dist/ai/ai.service.js +97 -3
- package/dist/ai/ai.service.js.map +1 -1
- package/dist/ai/dto/richtext-edit-html.dto.d.ts +5 -0
- package/dist/ai/dto/richtext-edit-html.dto.d.ts.map +1 -0
- package/dist/ai/dto/richtext-edit-html.dto.js +27 -0
- package/dist/ai/dto/richtext-edit-html.dto.js.map +1 -0
- package/dist/ai/richtext-ai.prompts.d.ts +5 -0
- package/dist/ai/richtext-ai.prompts.d.ts.map +1 -0
- package/dist/ai/richtext-ai.prompts.js +57 -0
- package/dist/ai/richtext-ai.prompts.js.map +1 -0
- package/dist/ai/richtext-ai.service.d.ts +34 -0
- package/dist/ai/richtext-ai.service.d.ts.map +1 -0
- package/dist/ai/richtext-ai.service.js +95 -0
- package/dist/ai/richtext-ai.service.js.map +1 -0
- package/dist/ai-instruction/ai-instruction.controller.d.ts +5 -5
- package/dist/auth/auth.controller.d.ts +66 -7
- package/dist/auth/auth.controller.d.ts.map +1 -1
- package/dist/auth/auth.controller.js +67 -23
- package/dist/auth/auth.controller.js.map +1 -1
- package/dist/auth/auth.module.d.ts.map +1 -1
- package/dist/auth/auth.module.js +2 -0
- package/dist/auth/auth.module.js.map +1 -1
- package/dist/auth/auth.service.d.ts +136 -4
- package/dist/auth/auth.service.d.ts.map +1 -1
- package/dist/auth/auth.service.js +246 -48
- package/dist/auth/auth.service.js.map +1 -1
- package/dist/auth/dto/confirm-email.dto.d.ts +4 -0
- package/dist/auth/dto/confirm-email.dto.d.ts.map +1 -0
- package/dist/auth/dto/confirm-email.dto.js +22 -0
- package/dist/auth/dto/confirm-email.dto.js.map +1 -0
- package/dist/auth/dto/resend-confirmation.dto.d.ts +8 -0
- package/dist/auth/dto/resend-confirmation.dto.d.ts.map +1 -0
- package/dist/auth/dto/resend-confirmation.dto.js +36 -0
- package/dist/auth/dto/resend-confirmation.dto.js.map +1 -0
- package/dist/authorize/authorize.controller.d.ts +1 -1
- package/dist/authorize/authorize.controller.d.ts.map +1 -1
- package/dist/authorize/authorize.controller.js +4 -2
- package/dist/authorize/authorize.controller.js.map +1 -1
- package/dist/authorize/authorize.service.d.ts +7 -0
- package/dist/authorize/authorize.service.d.ts.map +1 -1
- package/dist/authorize/authorize.service.js +1 -1
- package/dist/authorize/authorize.service.js.map +1 -1
- package/dist/challenge/challenge.service.d.ts +18 -5
- package/dist/challenge/challenge.service.d.ts.map +1 -1
- package/dist/challenge/challenge.service.js +50 -3
- package/dist/challenge/challenge.service.js.map +1 -1
- package/dist/core.module.d.ts.map +1 -1
- package/dist/core.module.js +17 -0
- package/dist/core.module.js.map +1 -1
- package/dist/dashboard/dashboard/dashboard.controller.d.ts +6 -6
- package/dist/dashboard/dashboard/dashboard.service.d.ts +6 -6
- package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +4 -4
- package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +4 -4
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts +1 -1
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +2 -2
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +2 -2
- package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts +2 -2
- package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts +2 -2
- package/dist/file/file.controller.d.ts +8 -1
- package/dist/file/file.controller.d.ts.map +1 -1
- package/dist/file/file.controller.js +45 -7
- package/dist/file/file.controller.js.map +1 -1
- package/dist/file/file.service.d.ts +128 -1
- package/dist/file/file.service.d.ts.map +1 -1
- package/dist/file/file.service.js +319 -8
- package/dist/file/file.service.js.map +1 -1
- package/dist/file/image-optimization.service.d.ts +43 -0
- package/dist/file/image-optimization.service.d.ts.map +1 -1
- package/dist/file/image-optimization.service.js +154 -0
- package/dist/file/image-optimization.service.js.map +1 -1
- package/dist/file/provider/abstract.provider.d.ts +16 -0
- package/dist/file/provider/abstract.provider.d.ts.map +1 -1
- package/dist/file/provider/abstract.provider.js +4 -0
- package/dist/file/provider/abstract.provider.js.map +1 -1
- package/dist/file/provider/gcs.provider.d.ts +2 -0
- package/dist/file/provider/gcs.provider.d.ts.map +1 -1
- package/dist/file/provider/gcs.provider.js +10 -0
- package/dist/file/provider/gcs.provider.js.map +1 -1
- package/dist/file/provider/local.provider.d.ts +1 -0
- package/dist/file/provider/local.provider.d.ts.map +1 -1
- package/dist/file/provider/local.provider.js +13 -0
- package/dist/file/provider/local.provider.js.map +1 -1
- package/dist/file/provider/s3.provider.d.ts +5 -0
- package/dist/file/provider/s3.provider.d.ts.map +1 -1
- package/dist/file/provider/s3.provider.js +15 -0
- package/dist/file/provider/s3.provider.js.map +1 -1
- package/dist/file/provider/spaces.provider.d.ts +5 -0
- package/dist/file/provider/spaces.provider.d.ts.map +1 -1
- package/dist/file/provider/spaces.provider.js +16 -0
- package/dist/file/provider/spaces.provider.js.map +1 -1
- package/dist/impersonation/dto/redeem-impersonation.dto.d.ts +4 -0
- package/dist/impersonation/dto/redeem-impersonation.dto.d.ts.map +1 -0
- package/dist/impersonation/dto/redeem-impersonation.dto.js +23 -0
- package/dist/impersonation/dto/redeem-impersonation.dto.js.map +1 -0
- package/dist/impersonation/dto/start-impersonation.dto.d.ts +7 -0
- package/dist/impersonation/dto/start-impersonation.dto.d.ts.map +1 -0
- package/dist/impersonation/dto/start-impersonation.dto.js +27 -0
- package/dist/impersonation/dto/start-impersonation.dto.js.map +1 -0
- package/dist/impersonation/impersonation-restrictions.d.ts +26 -0
- package/dist/impersonation/impersonation-restrictions.d.ts.map +1 -0
- package/dist/impersonation/impersonation-restrictions.js +76 -0
- package/dist/impersonation/impersonation-restrictions.js.map +1 -0
- package/dist/impersonation/impersonation.controller.d.ts +90 -0
- package/dist/impersonation/impersonation.controller.d.ts.map +1 -0
- package/dist/impersonation/impersonation.controller.js +135 -0
- package/dist/impersonation/impersonation.controller.js.map +1 -0
- package/dist/impersonation/impersonation.module.d.ts +8 -0
- package/dist/impersonation/impersonation.module.d.ts.map +1 -0
- package/dist/impersonation/impersonation.module.js +44 -0
- package/dist/impersonation/impersonation.module.js.map +1 -0
- package/dist/impersonation/impersonation.service.d.ts +164 -0
- package/dist/impersonation/impersonation.service.d.ts.map +1 -0
- package/dist/impersonation/impersonation.service.js +408 -0
- package/dist/impersonation/impersonation.service.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -1
- package/dist/integration/services/outbox-polling.coordinator.d.ts +2 -0
- package/dist/integration/services/outbox-polling.coordinator.d.ts.map +1 -1
- package/dist/integration/services/outbox-polling.coordinator.js +50 -30
- package/dist/integration/services/outbox-polling.coordinator.js.map +1 -1
- package/dist/integration/services/outbox.service.d.ts.map +1 -1
- package/dist/integration/services/outbox.service.js +5 -1
- package/dist/integration/services/outbox.service.js.map +1 -1
- package/dist/integration-profile/integration-profile.secrets.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.secrets.js +3 -0
- package/dist/integration-profile/integration-profile.secrets.js.map +1 -1
- package/dist/integration-profile/integration-profile.utils.d.ts +14 -0
- package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.utils.js +26 -0
- package/dist/integration-profile/integration-profile.utils.js.map +1 -1
- package/dist/integration-profile/testers/email.testers.d.ts.map +1 -1
- package/dist/integration-profile/testers/email.testers.js +28 -0
- package/dist/integration-profile/testers/email.testers.js.map +1 -1
- package/dist/language/en.json +11 -0
- package/dist/language/pt.json +11 -0
- package/dist/mail/dto/send.dto.d.ts +38 -1
- package/dist/mail/dto/send.dto.d.ts.map +1 -1
- package/dist/mail/dto/send.dto.js +10 -0
- package/dist/mail/dto/send.dto.js.map +1 -1
- package/dist/mail/mail.controller.d.ts +1 -0
- package/dist/mail/mail.controller.d.ts.map +1 -1
- package/dist/mail/mail.module.d.ts.map +1 -1
- package/dist/mail/mail.module.js +2 -0
- package/dist/mail/mail.module.js.map +1 -1
- package/dist/mail/mail.service.d.ts +48 -4
- package/dist/mail/mail.service.d.ts.map +1 -1
- package/dist/mail/mail.service.js +187 -35
- package/dist/mail/mail.service.js.map +1 -1
- package/dist/mail-delivery/mail-delivery-provision.service.d.ts +21 -0
- package/dist/mail-delivery/mail-delivery-provision.service.d.ts.map +1 -0
- package/dist/mail-delivery/mail-delivery-provision.service.js +64 -0
- package/dist/mail-delivery/mail-delivery-provision.service.js.map +1 -0
- package/dist/mail-delivery/mail-delivery.constants.d.ts +17 -0
- package/dist/mail-delivery/mail-delivery.constants.d.ts.map +1 -0
- package/dist/mail-delivery/mail-delivery.constants.js +31 -0
- package/dist/mail-delivery/mail-delivery.constants.js.map +1 -0
- package/dist/mail-delivery/mail-delivery.controller.d.ts +19 -0
- package/dist/mail-delivery/mail-delivery.controller.d.ts.map +1 -0
- package/dist/mail-delivery/mail-delivery.controller.js +53 -0
- package/dist/mail-delivery/mail-delivery.controller.js.map +1 -0
- package/dist/mail-delivery/mail-delivery.module.d.ts +7 -0
- package/dist/mail-delivery/mail-delivery.module.d.ts.map +1 -0
- package/dist/mail-delivery/mail-delivery.module.js +36 -0
- package/dist/mail-delivery/mail-delivery.module.js.map +1 -0
- package/dist/mail-delivery/mail-delivery.service.d.ts +38 -0
- package/dist/mail-delivery/mail-delivery.service.d.ts.map +1 -0
- package/dist/mail-delivery/mail-delivery.service.js +140 -0
- package/dist/mail-delivery/mail-delivery.service.js.map +1 -0
- package/dist/mail-delivery/ses-event.parser.d.ts +27 -0
- package/dist/mail-delivery/ses-event.parser.d.ts.map +1 -0
- package/dist/mail-delivery/ses-event.parser.js +131 -0
- package/dist/mail-delivery/ses-event.parser.js.map +1 -0
- package/dist/mail-delivery/ses-webhook.service.d.ts +58 -0
- package/dist/mail-delivery/ses-webhook.service.d.ts.map +1 -0
- package/dist/mail-delivery/ses-webhook.service.js +214 -0
- package/dist/mail-delivery/ses-webhook.service.js.map +1 -0
- package/dist/mail-delivery/sns-message.util.d.ts +38 -0
- package/dist/mail-delivery/sns-message.util.d.ts.map +1 -0
- package/dist/mail-delivery/sns-message.util.js +109 -0
- package/dist/mail-delivery/sns-message.util.js.map +1 -0
- package/dist/mail-sent/dto/query-mail-sent-list.dto.d.ts +5 -0
- package/dist/mail-sent/dto/query-mail-sent-list.dto.d.ts.map +1 -1
- package/dist/mail-sent/dto/query-mail-sent-list.dto.js +6 -0
- package/dist/mail-sent/dto/query-mail-sent-list.dto.js.map +1 -1
- package/dist/mail-sent/mail-sent.controller.d.ts +39 -0
- package/dist/mail-sent/mail-sent.controller.d.ts.map +1 -1
- package/dist/mail-sent/mail-sent.service.d.ts +39 -0
- package/dist/mail-sent/mail-sent.service.d.ts.map +1 -1
- package/dist/mail-sent/mail-sent.service.js +45 -4
- package/dist/mail-sent/mail-sent.service.js.map +1 -1
- package/dist/mailbox/mailbox-fetcher.registry.d.ts +100 -0
- package/dist/mailbox/mailbox-fetcher.registry.d.ts.map +1 -0
- package/dist/mailbox/mailbox-fetcher.registry.js +59 -0
- package/dist/mailbox/mailbox-fetcher.registry.js.map +1 -0
- package/dist/mailbox/mailbox.module.d.ts +8 -0
- package/dist/mailbox/mailbox.module.d.ts.map +1 -0
- package/dist/mailbox/mailbox.module.js +27 -0
- package/dist/mailbox/mailbox.module.js.map +1 -0
- package/dist/meeting/meeting-provider.registry.d.ts +155 -0
- package/dist/meeting/meeting-provider.registry.d.ts.map +1 -0
- package/dist/meeting/meeting-provider.registry.js +53 -0
- package/dist/meeting/meeting-provider.registry.js.map +1 -0
- package/dist/meeting/meeting.constants.d.ts +47 -0
- package/dist/meeting/meeting.constants.d.ts.map +1 -0
- package/dist/meeting/meeting.constants.js +21 -0
- package/dist/meeting/meeting.constants.js.map +1 -0
- package/dist/meeting/meeting.module.d.ts +7 -0
- package/dist/meeting/meeting.module.d.ts.map +1 -0
- package/dist/meeting/meeting.module.js +26 -0
- package/dist/meeting/meeting.module.js.map +1 -0
- package/dist/middlewares/locale-injection.middleware.d.ts +12 -0
- package/dist/middlewares/locale-injection.middleware.d.ts.map +1 -1
- package/dist/middlewares/locale-injection.middleware.js +20 -4
- package/dist/middlewares/locale-injection.middleware.js.map +1 -1
- package/dist/oauth/providers/google.provider.d.ts +7 -0
- package/dist/oauth/providers/google.provider.d.ts.map +1 -1
- package/dist/oauth/providers/google.provider.js +43 -13
- package/dist/oauth/providers/google.provider.js.map +1 -1
- package/dist/observability/monitored-cron.d.ts +34 -0
- package/dist/observability/monitored-cron.d.ts.map +1 -0
- package/dist/observability/monitored-cron.js +118 -0
- package/dist/observability/monitored-cron.js.map +1 -0
- package/dist/pairing/dto/approve-reverse-pairing.dto.d.ts +12 -0
- package/dist/pairing/dto/approve-reverse-pairing.dto.d.ts.map +1 -0
- package/dist/pairing/dto/approve-reverse-pairing.dto.js +30 -0
- package/dist/pairing/dto/approve-reverse-pairing.dto.js.map +1 -0
- package/dist/pairing/dto/claim-pairing.dto.d.ts +11 -0
- package/dist/pairing/dto/claim-pairing.dto.d.ts.map +1 -0
- package/dist/pairing/dto/claim-pairing.dto.js +35 -0
- package/dist/pairing/dto/claim-pairing.dto.js.map +1 -0
- package/dist/pairing/dto/confirm-pairing.dto.d.ts +10 -0
- package/dist/pairing/dto/confirm-pairing.dto.d.ts.map +1 -0
- package/dist/pairing/dto/confirm-pairing.dto.js +24 -0
- package/dist/pairing/dto/confirm-pairing.dto.js.map +1 -0
- package/dist/pairing/dto/poll-reverse-pairing.dto.d.ts +5 -0
- package/dist/pairing/dto/poll-reverse-pairing.dto.d.ts.map +1 -0
- package/dist/pairing/dto/poll-reverse-pairing.dto.js +24 -0
- package/dist/pairing/dto/poll-reverse-pairing.dto.js.map +1 -0
- package/dist/pairing/dto/start-pairing.dto.d.ts +11 -0
- package/dist/pairing/dto/start-pairing.dto.d.ts.map +1 -0
- package/dist/pairing/dto/start-pairing.dto.js +30 -0
- package/dist/pairing/dto/start-pairing.dto.js.map +1 -0
- package/dist/pairing/dto/start-reverse-pairing.dto.d.ts +24 -0
- package/dist/pairing/dto/start-reverse-pairing.dto.d.ts.map +1 -0
- package/dist/pairing/dto/start-reverse-pairing.dto.js +47 -0
- package/dist/pairing/dto/start-reverse-pairing.dto.js.map +1 -0
- package/dist/pairing/pairing.controller.d.ts +118 -0
- package/dist/pairing/pairing.controller.d.ts.map +1 -0
- package/dist/pairing/pairing.controller.js +296 -0
- package/dist/pairing/pairing.controller.js.map +1 -0
- package/dist/pairing/pairing.module.d.ts +10 -0
- package/dist/pairing/pairing.module.d.ts.map +1 -0
- package/dist/pairing/pairing.module.js +40 -0
- package/dist/pairing/pairing.module.js.map +1 -0
- package/dist/pairing/pairing.service.d.ts +238 -0
- package/dist/pairing/pairing.service.d.ts.map +1 -0
- package/dist/pairing/pairing.service.js +495 -0
- package/dist/pairing/pairing.service.js.map +1 -0
- package/dist/pdf/pdf.service.d.ts +3 -6
- package/dist/pdf/pdf.service.d.ts.map +1 -1
- package/dist/pdf/pdf.service.js +8 -20
- package/dist/pdf/pdf.service.js.map +1 -1
- package/dist/profile/profile.controller.d.ts +11 -0
- package/dist/profile/profile.controller.d.ts.map +1 -1
- package/dist/profile/profile.service.d.ts +11 -0
- package/dist/profile/profile.service.d.ts.map +1 -1
- package/dist/profile/profile.service.js +20 -11
- package/dist/profile/profile.service.js.map +1 -1
- package/dist/redis/redis-pubsub.d.ts.map +1 -1
- package/dist/redis/redis-pubsub.js +44 -0
- package/dist/redis/redis-pubsub.js.map +1 -1
- package/dist/role/guards/role.guard.d.ts +5 -0
- package/dist/role/guards/role.guard.d.ts.map +1 -1
- package/dist/role/guards/role.guard.js +41 -39
- package/dist/role/guards/role.guard.js.map +1 -1
- package/dist/session/session.controller.d.ts +2 -0
- package/dist/session/session.controller.d.ts.map +1 -1
- package/dist/session/session.service.d.ts +39 -0
- package/dist/session/session.service.d.ts.map +1 -1
- package/dist/session/session.service.js +91 -8
- package/dist/session/session.service.js.map +1 -1
- package/dist/setting/brand-asset.service.d.ts +48 -0
- package/dist/setting/brand-asset.service.d.ts.map +1 -0
- package/dist/setting/brand-asset.service.js +130 -0
- package/dist/setting/brand-asset.service.js.map +1 -0
- package/dist/setting/setting.module.d.ts.map +1 -1
- package/dist/setting/setting.module.js +3 -2
- package/dist/setting/setting.module.js.map +1 -1
- package/dist/setting/setting.service.d.ts +33 -0
- package/dist/setting/setting.service.d.ts.map +1 -1
- package/dist/setting/setting.service.js +88 -0
- package/dist/setting/setting.service.js.map +1 -1
- package/dist/token/token.service.d.ts +23 -2
- package/dist/token/token.service.d.ts.map +1 -1
- package/dist/token/token.service.js +39 -4
- package/dist/token/token.service.js.map +1 -1
- package/dist/user/user.controller.d.ts +8 -22
- package/dist/user/user.controller.d.ts.map +1 -1
- package/dist/user/user.service.d.ts +44 -24
- package/dist/user/user.service.d.ts.map +1 -1
- package/dist/user/user.service.js +89 -6
- package/dist/user/user.service.js.map +1 -1
- package/dist/user-device/user-device.controller.d.ts +54 -0
- package/dist/user-device/user-device.controller.d.ts.map +1 -0
- package/dist/user-device/user-device.controller.js +103 -0
- package/dist/user-device/user-device.controller.js.map +1 -0
- package/dist/user-device/user-device.module.d.ts +3 -0
- package/dist/user-device/user-device.module.d.ts.map +1 -0
- package/dist/user-device/user-device.module.js +26 -0
- package/dist/user-device/user-device.module.js.map +1 -0
- package/dist/user-device/user-device.service.d.ts +71 -0
- package/dist/user-device/user-device.service.d.ts.map +1 -0
- package/dist/user-device/user-device.service.js +158 -0
- package/dist/user-device/user-device.service.js.map +1 -0
- package/dist/utils/device-key.d.ts +23 -0
- package/dist/utils/device-key.d.ts.map +1 -0
- package/dist/utils/device-key.js +31 -0
- package/dist/utils/device-key.js.map +1 -0
- package/dist/utils/geo-ip.d.ts +38 -0
- package/dist/utils/geo-ip.d.ts.map +1 -0
- package/dist/utils/geo-ip.js +122 -0
- package/dist/utils/geo-ip.js.map +1 -0
- package/dist/utils/locale-context.d.ts +10 -7
- package/dist/utils/locale-context.d.ts.map +1 -1
- package/dist/utils/locale-context.js +14 -8
- package/dist/utils/locale-context.js.map +1 -1
- package/dist/utils/security-event-vars.d.ts +56 -0
- package/dist/utils/security-event-vars.d.ts.map +1 -0
- package/dist/utils/security-event-vars.js +109 -0
- package/dist/utils/security-event-vars.js.map +1 -0
- package/dist/utils/user-agent.d.ts +19 -0
- package/dist/utils/user-agent.d.ts.map +1 -0
- package/dist/utils/user-agent.js +36 -0
- package/dist/utils/user-agent.js.map +1 -0
- package/dist/webhook-integration/webhook-integration.service.d.ts +5 -0
- package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-integration.service.js +50 -4
- package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
- package/dist/webhook-integration/webhook-protocol.registry.d.ts +6 -0
- package/dist/webhook-integration/webhook-protocol.registry.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-protocol.registry.js.map +1 -1
- package/hedhog/data/integration_provider.yaml +9 -0
- package/hedhog/data/mail.yaml +198 -62
- package/hedhog/data/mail_layout.yaml +43 -33
- package/hedhog/data/menu.yaml +2 -0
- package/hedhog/data/role.yaml +16 -0
- package/hedhog/data/route.yaml +172 -3
- package/hedhog/data/setting_group.yaml +178 -2
- package/hedhog/data/setting_subgroup.yaml +8 -0
- package/hedhog/frontend/app/_lib/hooks/use-core-access.test.tsx.ejs +125 -0
- package/hedhog/frontend/app/_lib/hooks/use-core-access.ts.ejs +29 -0
- package/hedhog/frontend/app/_lib/hooks/use-impersonation-access.test.ts.ejs +71 -0
- package/hedhog/frontend/app/_lib/hooks/use-impersonation-access.ts.ejs +32 -0
- package/hedhog/frontend/app/access-log/page.tsx.ejs +96 -22
- package/hedhog/frontend/app/account/components/active-sessions.tsx.ejs +17 -1
- package/hedhog/frontend/app/account/components/known-devices.tsx.ejs +89 -0
- package/hedhog/frontend/app/account/components/profile-form.tsx.ejs +2 -1
- package/hedhog/frontend/app/account/components/user-devices-list.tsx.ejs +186 -0
- package/hedhog/frontend/app/account/devices/page.tsx.ejs +5 -0
- package/hedhog/frontend/app/account/layout.tsx.ejs +2 -0
- package/hedhog/frontend/app/configurations/[slug]/components/setting-field.test.tsx.ejs +298 -0
- package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +376 -1236
- package/hedhog/frontend/app/mail/log/page.tsx.ejs +210 -3
- package/hedhog/frontend/app/mail/template/page.tsx.ejs +190 -132
- package/hedhog/frontend/app/users/active-session.tsx.ejs +58 -11
- package/hedhog/frontend/app/users/page.tsx.ejs +69 -10
- package/hedhog/frontend/app/users/simulate-access-dialog.test.tsx.ejs +202 -0
- package/hedhog/frontend/app/users/simulate-access-dialog.tsx.ejs +170 -0
- package/hedhog/frontend/app/users/user-activity-timeline.tsx.ejs +19 -0
- package/hedhog/frontend/app/users/user-devices.tsx.ejs +89 -0
- package/hedhog/frontend/app/users/user-logs.tsx.ejs +13 -1
- package/hedhog/frontend/app/users/user-sheet.tsx.ejs +46 -24
- package/hedhog/frontend/messages/en.json +146 -48
- package/hedhog/frontend/messages/pt.json +146 -48
- package/hedhog/table/access_log.yaml +7 -0
- package/hedhog/table/file.yaml +8 -0
- package/hedhog/table/mail_delivery_event.yaml +76 -0
- package/hedhog/table/mail_sent.yaml +23 -0
- package/hedhog/table/user_activity.yaml +9 -0
- package/hedhog/table/user_device.yaml +52 -0
- package/hedhog/table/user_session.yaml +20 -1
- package/package.json +6 -5
- package/src/access-log/access-log.controller.ts +1 -0
- package/src/access-log/access-log.interceptor.ts +5 -1
- package/src/access-log/access-log.service.spec.ts +33 -2
- package/src/access-log/access-log.service.ts +38 -0
- package/src/access-log/dto/query-access-log.dto.ts +5 -0
- package/src/ai/ai.controller.ts +9 -0
- package/src/ai/ai.module.ts +3 -2
- package/src/ai/ai.service.ts +150 -13
- package/src/ai/dto/richtext-edit-html.dto.ts +11 -0
- package/src/ai/richtext-ai.prompts.ts +57 -0
- package/src/ai/richtext-ai.service.ts +109 -0
- package/src/auth/auth.controller.ts +47 -8
- package/src/auth/auth.module.ts +2 -0
- package/src/auth/auth.service.spec.ts +383 -6
- package/src/auth/auth.service.ts +345 -56
- package/src/auth/dto/confirm-email.dto.ts +7 -0
- package/src/auth/dto/resend-confirmation.dto.ts +20 -0
- package/src/authorize/authorize.controller.ts +2 -0
- package/src/authorize/authorize.service.spec.ts +23 -0
- package/src/authorize/authorize.service.ts +8 -0
- package/src/challenge/challenge.service.spec.ts +73 -0
- package/src/challenge/challenge.service.ts +62 -3
- package/src/core.module.ts +19 -0
- package/src/file/file.controller.ts +54 -4
- package/src/file/file.service.spec.ts +275 -105
- package/src/file/file.service.ts +409 -14
- package/src/file/image-optimization.service.spec.ts +92 -0
- package/src/file/image-optimization.service.ts +202 -0
- package/src/file/provider/abstract.provider.ts +23 -0
- package/src/file/provider/gcs.provider.ts +17 -0
- package/src/file/provider/local.provider.ts +21 -0
- package/src/file/provider/s3.provider.ts +25 -0
- package/src/file/provider/spaces.provider.ts +26 -0
- package/src/impersonation/dto/redeem-impersonation.dto.ts +8 -0
- package/src/impersonation/dto/start-impersonation.dto.ts +13 -0
- package/src/impersonation/impersonation-restrictions.ts +84 -0
- package/src/impersonation/impersonation.controller.ts +109 -0
- package/src/impersonation/impersonation.module.ts +31 -0
- package/src/impersonation/impersonation.service.spec.ts +427 -0
- package/src/impersonation/impersonation.service.ts +577 -0
- package/src/index.ts +21 -0
- package/src/integration/services/outbox-polling.coordinator.spec.ts +90 -0
- package/src/integration/services/outbox-polling.coordinator.ts +62 -40
- package/src/integration/services/outbox.service.spec.ts +8 -0
- package/src/integration/services/outbox.service.ts +19 -13
- package/src/integration-profile/integration-profile.secrets.ts +3 -0
- package/src/integration-profile/integration-profile.utils.ts +38 -0
- package/src/integration-profile/testers/email.testers.ts +34 -1
- package/src/language/en.json +11 -0
- package/src/language/pt.json +11 -0
- package/src/mail/dto/send.dto.ts +46 -2
- package/src/mail/mail-template.spec.ts +79 -2
- package/src/mail/mail.module.ts +2 -0
- package/src/mail/mail.service.spec.ts +307 -6
- package/src/mail/mail.service.ts +227 -30
- package/src/mail-delivery/mail-delivery-provision.service.ts +66 -0
- package/src/mail-delivery/mail-delivery.constants.ts +31 -0
- package/src/mail-delivery/mail-delivery.controller.ts +38 -0
- package/src/mail-delivery/mail-delivery.module.ts +23 -0
- package/src/mail-delivery/mail-delivery.service.spec.ts +136 -0
- package/src/mail-delivery/mail-delivery.service.ts +152 -0
- package/src/mail-delivery/ses-event.parser.spec.ts +140 -0
- package/src/mail-delivery/ses-event.parser.ts +191 -0
- package/src/mail-delivery/ses-webhook.service.spec.ts +170 -0
- package/src/mail-delivery/ses-webhook.service.ts +240 -0
- package/src/mail-delivery/sns-message.util.spec.ts +163 -0
- package/src/mail-delivery/sns-message.util.ts +125 -0
- package/src/mail-sent/dto/query-mail-sent-list.dto.ts +15 -0
- package/src/mail-sent/mail-sent.service.ts +46 -2
- package/src/mailbox/mailbox-fetcher.registry.ts +132 -0
- package/src/mailbox/mailbox.module.ts +14 -0
- package/src/meeting/meeting-provider.registry.ts +193 -0
- package/src/meeting/meeting.constants.ts +53 -0
- package/src/meeting/meeting.module.ts +13 -0
- package/src/middlewares/locale-injection.middleware.ts +25 -6
- package/src/oauth/providers/google.provider.ts +49 -15
- package/src/observability/monitored-cron.spec.ts +102 -0
- package/src/observability/monitored-cron.ts +110 -0
- package/src/pairing/dto/approve-reverse-pairing.dto.ts +20 -0
- package/src/pairing/dto/claim-pairing.dto.ts +19 -0
- package/src/pairing/dto/confirm-pairing.dto.ts +14 -0
- package/src/pairing/dto/poll-reverse-pairing.dto.ts +9 -0
- package/src/pairing/dto/start-pairing.dto.ts +19 -0
- package/src/pairing/dto/start-reverse-pairing.dto.ts +45 -0
- package/src/pairing/pairing.controller.ts +246 -0
- package/src/pairing/pairing.module.ts +27 -0
- package/src/pairing/pairing.service.spec.ts +462 -0
- package/src/pairing/pairing.service.ts +614 -0
- package/src/pdf/pdf.service.ts +6 -24
- package/src/profile/profile.service.ts +26 -8
- package/src/redis/redis-pubsub.ts +11 -0
- package/src/role/guards/role.guard.spec.ts +75 -0
- package/src/role/guards/role.guard.ts +64 -46
- package/src/session/session.service.spec.ts +81 -0
- package/src/session/session.service.ts +93 -6
- package/src/setting/brand-asset.service.ts +137 -0
- package/src/setting/setting.module.ts +3 -2
- package/src/setting/setting.service.spec.ts +79 -1
- package/src/setting/setting.service.ts +109 -1
- package/src/token/token.service.spec.ts +62 -0
- package/src/token/token.service.ts +57 -3
- package/src/user/user.service.spec.ts +33 -2
- package/src/user/user.service.ts +108 -6
- package/src/user-device/user-device.controller.ts +77 -0
- package/src/user-device/user-device.module.ts +13 -0
- package/src/user-device/user-device.service.spec.ts +234 -0
- package/src/user-device/user-device.service.ts +168 -0
- package/src/utils/device-key.spec.ts +57 -0
- package/src/utils/device-key.ts +32 -0
- package/src/utils/geo-ip.spec.ts +134 -0
- package/src/utils/geo-ip.ts +93 -0
- package/src/utils/locale-context.spec.ts +27 -1
- package/src/utils/locale-context.ts +15 -12
- package/src/utils/security-event-vars.spec.ts +161 -0
- package/src/utils/security-event-vars.ts +153 -0
- package/src/utils/user-agent.spec.ts +37 -0
- package/src/utils/user-agent.ts +46 -0
- package/src/webhook-integration/webhook-integration.service.ts +57 -3
- package/src/webhook-integration/webhook-protocol.registry.ts +6 -0
package/src/file/file.service.ts
CHANGED
|
@@ -31,9 +31,12 @@ import {
|
|
|
31
31
|
OnModuleInit
|
|
32
32
|
} from '@nestjs/common';
|
|
33
33
|
import { JwtService } from '@nestjs/jwt';
|
|
34
|
+
import type { Request } from 'express';
|
|
34
35
|
import { createWriteStream, promises as fs } from 'fs';
|
|
35
|
-
import {
|
|
36
|
+
import { tmpdir } from 'os';
|
|
37
|
+
import { basename, dirname, extname, join } from 'path';
|
|
36
38
|
import { pipeline } from 'stream/promises';
|
|
39
|
+
import { v4 } from 'uuid';
|
|
37
40
|
import {
|
|
38
41
|
buildStorageConfigFromIntegration,
|
|
39
42
|
resolveFileProviderSlug,
|
|
@@ -41,8 +44,14 @@ import {
|
|
|
41
44
|
} from '../integration-profile/integration-profile.utils';
|
|
42
45
|
import { SettingService } from '../setting/setting.service';
|
|
43
46
|
import { DeleteDTO } from './dto/delete.dto';
|
|
44
|
-
import {
|
|
45
|
-
|
|
47
|
+
import {
|
|
48
|
+
ImageOptimizationService,
|
|
49
|
+
ImageVariantFormat,
|
|
50
|
+
} from './image-optimization.service';
|
|
51
|
+
import {
|
|
52
|
+
AbstractProvider,
|
|
53
|
+
supportsServerSideCopy,
|
|
54
|
+
} from './provider/abstract.provider';
|
|
46
55
|
import { ProviderFactory } from './provider/provider.factory';
|
|
47
56
|
|
|
48
57
|
type ResolvedProvider = {
|
|
@@ -63,6 +72,13 @@ const PROVIDER_CACHE_TTL_MS = Math.max(
|
|
|
63
72
|
Number(process.env.FILE_PROVIDER_CACHE_TTL_MS ?? 30_000),
|
|
64
73
|
);
|
|
65
74
|
|
|
75
|
+
/** Raw `?w=`/`?f=`/`?q=` query values for the public image endpoint. */
|
|
76
|
+
export type ImageVariantRequest = {
|
|
77
|
+
width?: number | string;
|
|
78
|
+
format?: string;
|
|
79
|
+
quality?: number | string;
|
|
80
|
+
};
|
|
81
|
+
|
|
66
82
|
@Injectable()
|
|
67
83
|
export class FileService implements OnModuleInit {
|
|
68
84
|
private readonly logger = new Logger(FileService.name);
|
|
@@ -467,6 +483,50 @@ export class FileService implements OnModuleInit {
|
|
|
467
483
|
};
|
|
468
484
|
}
|
|
469
485
|
|
|
486
|
+
/**
|
|
487
|
+
* Renderiza um cartão social 1200x630 (JPEG) a partir de arquivos do acervo.
|
|
488
|
+
*
|
|
489
|
+
* Devolve `null` quando não há arquivo utilizável — quem chama deve responder
|
|
490
|
+
* 404 e deixar o consumidor cair na imagem padrão da marca, nunca 500: do
|
|
491
|
+
* outro lado costuma haver um robô de link preview.
|
|
492
|
+
*/
|
|
493
|
+
async renderShareCard(options: {
|
|
494
|
+
backgroundFileId?: number | null;
|
|
495
|
+
overlayFileId?: number | null;
|
|
496
|
+
background?: string | null;
|
|
497
|
+
}): Promise<{ buffer: Buffer; contentType: string } | null> {
|
|
498
|
+
const load = async (fileId?: number | null): Promise<Buffer | null> => {
|
|
499
|
+
if (!fileId) return null;
|
|
500
|
+
try {
|
|
501
|
+
const { buffer } = await this.getBuffer(fileId);
|
|
502
|
+
return buffer;
|
|
503
|
+
} catch {
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
const [background, overlay] = await Promise.all([
|
|
509
|
+
load(options.backgroundFileId),
|
|
510
|
+
load(options.overlayFileId),
|
|
511
|
+
]);
|
|
512
|
+
|
|
513
|
+
if (!background && !overlay) {
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const buffer = await this.imageOptimizationService.composeShareCard(
|
|
518
|
+
background,
|
|
519
|
+
overlay,
|
|
520
|
+
{ background: options.background },
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
if (!buffer) {
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return { buffer, contentType: 'image/jpeg' };
|
|
528
|
+
}
|
|
529
|
+
|
|
470
530
|
async getLocalFile(fileId: number, res: any){
|
|
471
531
|
const { buffer, file } = await this.getBuffer(fileId);
|
|
472
532
|
res.set({
|
|
@@ -483,7 +543,12 @@ export class FileService implements OnModuleInit {
|
|
|
483
543
|
// Escape hatch para chamadores INTERNOS que já validaram por conta própria —
|
|
484
544
|
// o mesmo par de flags que `uploadFromPath` sempre teve. O FileController não
|
|
485
545
|
// repassa este argumento, então não há como acioná-lo por HTTP.
|
|
486
|
-
|
|
546
|
+
// `isPrivate` tira o arquivo das rotas que servem por id sem autenticação.
|
|
547
|
+
options?: {
|
|
548
|
+
skipMimeValidation?: boolean;
|
|
549
|
+
skipSizeValidation?: boolean;
|
|
550
|
+
isPrivate?: boolean;
|
|
551
|
+
},
|
|
487
552
|
) {
|
|
488
553
|
|
|
489
554
|
if (!fileBuffer) {
|
|
@@ -544,6 +609,7 @@ export class FileService implements OnModuleInit {
|
|
|
544
609
|
location: destination,
|
|
545
610
|
mimetype_id: await this.getMimeType(optimized.mimetype),
|
|
546
611
|
size: optimized.buffer.length,
|
|
612
|
+
is_private: options?.isPrivate ?? false,
|
|
547
613
|
},
|
|
548
614
|
});
|
|
549
615
|
|
|
@@ -561,6 +627,10 @@ export class FileService implements OnModuleInit {
|
|
|
561
627
|
encoding?: string;
|
|
562
628
|
skipMimeValidation?: boolean;
|
|
563
629
|
skipSizeValidation?: boolean;
|
|
630
|
+
// Tira o arquivo das rotas que servem por id sem autenticação.
|
|
631
|
+
isPrivate?: boolean;
|
|
632
|
+
// Mesmo par maxWidth/maxHeight que `upload` aceita — só tem efeito em image/*.
|
|
633
|
+
resize?: { maxWidth?: number; maxHeight?: number };
|
|
564
634
|
},
|
|
565
635
|
) {
|
|
566
636
|
if (!filePath) {
|
|
@@ -616,6 +686,7 @@ export class FileService implements OnModuleInit {
|
|
|
616
686
|
location: destination,
|
|
617
687
|
mimetype_id: await this.getMimeType(mimetype),
|
|
618
688
|
size,
|
|
689
|
+
is_private: metadata?.isPrivate ?? false,
|
|
619
690
|
},
|
|
620
691
|
});
|
|
621
692
|
}
|
|
@@ -626,6 +697,10 @@ export class FileService implements OnModuleInit {
|
|
|
626
697
|
rawBuffer,
|
|
627
698
|
mimetype,
|
|
628
699
|
originalname,
|
|
700
|
+
{
|
|
701
|
+
maxWidth: metadata?.resize?.maxWidth,
|
|
702
|
+
maxHeight: metadata?.resize?.maxHeight,
|
|
703
|
+
},
|
|
629
704
|
);
|
|
630
705
|
|
|
631
706
|
const url = await provider.upload(destination, {
|
|
@@ -648,6 +723,7 @@ export class FileService implements OnModuleInit {
|
|
|
648
723
|
location: destination,
|
|
649
724
|
mimetype_id: await this.getMimeType(optimized.mimetype),
|
|
650
725
|
size: optimized.buffer.length,
|
|
726
|
+
is_private: metadata?.isPrivate ?? false,
|
|
651
727
|
},
|
|
652
728
|
});
|
|
653
729
|
|
|
@@ -804,10 +880,125 @@ export class FileService implements OnModuleInit {
|
|
|
804
880
|
return file;
|
|
805
881
|
}
|
|
806
882
|
|
|
883
|
+
/**
|
|
884
|
+
* Creates an independent copy of a stored file: new bytes in storage + a new `file` row.
|
|
885
|
+
*
|
|
886
|
+
* Unlike sharing a `file_id`, the result survives the original being deleted — which is what
|
|
887
|
+
* "duplicate files" means when copying course content between courses.
|
|
888
|
+
*
|
|
889
|
+
* `filename` and `destination` default to the source's, so a caller that only wants
|
|
890
|
+
* independent bytes gets them without thinking about paths. Passing `destination` matters for
|
|
891
|
+
* artifacts addressed by their location (HLS manifests and segments).
|
|
892
|
+
*
|
|
893
|
+
* The bytes are copied server-side when the active provider supports it; otherwise — and when
|
|
894
|
+
* the provider-side copy fails, e.g. an object above S3's 5GB CopyObject cap — they are
|
|
895
|
+
* streamed through a temp file, never buffered in RAM. Mime/size validation is skipped: the
|
|
896
|
+
* source already passed it at upload time.
|
|
897
|
+
*/
|
|
898
|
+
async duplicate(
|
|
899
|
+
fileId: number,
|
|
900
|
+
options?: { destination?: string; filename?: string },
|
|
901
|
+
) {
|
|
902
|
+
const source = await this.prismaService.file.findUnique({
|
|
903
|
+
where: { id: fileId },
|
|
904
|
+
include: { file_mimetype: true },
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
if (!source) {
|
|
908
|
+
throw new NotFoundException(`File not found: ${fileId}`);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
const destination = options?.destination ?? source.location;
|
|
912
|
+
const filename = options?.filename ?? source.filename;
|
|
913
|
+
const sourcePath = this.resolveProviderPath(source);
|
|
914
|
+
const provider = await this.getProvider();
|
|
915
|
+
|
|
916
|
+
let storedFilename: string | null = null;
|
|
917
|
+
|
|
918
|
+
if (supportsServerSideCopy(provider)) {
|
|
919
|
+
try {
|
|
920
|
+
storedFilename = await provider.copy(sourcePath, destination, filename);
|
|
921
|
+
} catch (error) {
|
|
922
|
+
this.logger.warn(
|
|
923
|
+
`Server-side copy failed for file ${fileId}, falling back to streaming: ${
|
|
924
|
+
error instanceof Error ? error.message : String(error)
|
|
925
|
+
}`,
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
if (storedFilename === null) {
|
|
931
|
+
const tempPath = join(tmpdir(), `hedhog-file-copy-${v4()}`);
|
|
932
|
+
try {
|
|
933
|
+
await this.downloadToPath(fileId, tempPath);
|
|
934
|
+
storedFilename = await provider.uploadFromPath(destination, tempPath, {
|
|
935
|
+
filename,
|
|
936
|
+
mimetype: source.file_mimetype?.name,
|
|
937
|
+
});
|
|
938
|
+
} finally {
|
|
939
|
+
await fs.unlink(tempPath).catch(() => undefined);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
return this.prismaService.file.create({
|
|
944
|
+
data: {
|
|
945
|
+
filename,
|
|
946
|
+
path: storedFilename,
|
|
947
|
+
provider_id: this.providerId,
|
|
948
|
+
location: destination,
|
|
949
|
+
mimetype_id: source.mimetype_id,
|
|
950
|
+
size: source.size ?? 0,
|
|
951
|
+
},
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
|
|
807
955
|
async readStream(filepath: string) {
|
|
808
956
|
return (await this.getProvider()).readStream(filepath);
|
|
809
957
|
}
|
|
810
958
|
|
|
959
|
+
/**
|
|
960
|
+
* Confere se os bytes de um `file` estão mesmo no storage, devolvendo o tamanho
|
|
961
|
+
* relatado pelo provider ao lado do tamanho declarado no banco.
|
|
962
|
+
*
|
|
963
|
+
* Existe para quem precisa **provar** que a cópia sobreviveu antes de apagar o
|
|
964
|
+
* original em outro lugar: o registro em banco sozinho não é prova, porque é
|
|
965
|
+
* gravado depois do upload e nada reconcilia os dois se o insert falhar no meio
|
|
966
|
+
* — o blob fica órfão ou a linha aponta para o vazio, e ninguém percebe até
|
|
967
|
+
* alguém tentar ler.
|
|
968
|
+
*
|
|
969
|
+
* Não lança quando o objeto está ausente: devolve `storedSize: null`, para o
|
|
970
|
+
* chamador decidir o que fazer com a divergência.
|
|
971
|
+
*/
|
|
972
|
+
async statStoredFile(
|
|
973
|
+
fileId: number,
|
|
974
|
+
): Promise<{ storedSize: number | null; declaredSize: number }> {
|
|
975
|
+
if (!fileId) {
|
|
976
|
+
throw new BadRequestException(`No file ID provided`);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
const file = await this.prismaService.file.findUnique({
|
|
980
|
+
where: { id: fileId },
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
if (!file) {
|
|
984
|
+
throw new NotFoundException(`File not found: ${fileId}`);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
const provider = await this.getProvider();
|
|
988
|
+
|
|
989
|
+
try {
|
|
990
|
+
const metadata = await provider.metaData(this.resolveProviderPath(file));
|
|
991
|
+
return {
|
|
992
|
+
storedSize: this.getFileSizeFromMetadata(metadata) ?? null,
|
|
993
|
+
declaredSize: Number(file.size ?? 0),
|
|
994
|
+
};
|
|
995
|
+
} catch {
|
|
996
|
+
// Qualquer falha de leitura de metadado — ausente, sem permissão, provider
|
|
997
|
+
// fora do ar — é tratada como "não deu para provar", nunca como sucesso.
|
|
998
|
+
return { storedSize: null, declaredSize: Number(file.size ?? 0) };
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
811
1002
|
private getFileSizeFromMetadata(metadata: any): number | undefined {
|
|
812
1003
|
if (!metadata) {
|
|
813
1004
|
return undefined;
|
|
@@ -910,13 +1101,25 @@ export class FileService implements OnModuleInit {
|
|
|
910
1101
|
* Resolves the file record and its storage metadata (mimetype + size) without
|
|
911
1102
|
* opening a read stream — so callers can answer a conditional 304 cheaply.
|
|
912
1103
|
*/
|
|
1104
|
+
/**
|
|
1105
|
+
* Metadados para servir um arquivo POR ID.
|
|
1106
|
+
*
|
|
1107
|
+
* Só os dois handlers anônimos passam por aqui (`serveFileById` e
|
|
1108
|
+
* `serveImageById`), e é por isso que a checagem de `is_private` mora neste
|
|
1109
|
+
* ponto e não em cada um: id de arquivo é sequencial, então servir por id é uma
|
|
1110
|
+
* porta enumerável. O caminho por token (`download`) não passa por aqui, então
|
|
1111
|
+
* um arquivo privado continua acessível ao admin via
|
|
1112
|
+
* `PUT /file/open/:id` -> token -> `GET /file/open/:token`.
|
|
1113
|
+
*/
|
|
913
1114
|
private async resolveFileMeta(locale: string, id: number) {
|
|
914
1115
|
const file = await this.prismaService.file.findFirst({
|
|
915
1116
|
where: { id },
|
|
916
1117
|
include: { file_mimetype: true },
|
|
917
1118
|
});
|
|
918
1119
|
|
|
919
|
-
|
|
1120
|
+
// Privado responde igual a inexistente: confirmar que o id existe já
|
|
1121
|
+
// entregaria metade do que a enumeração procura.
|
|
1122
|
+
if (!file || file.is_private) {
|
|
920
1123
|
throw new NotFoundException(
|
|
921
1124
|
getLocaleText('file.download.not_found', locale, `Not found: ${id}`),
|
|
922
1125
|
);
|
|
@@ -1010,16 +1213,82 @@ export class FileService implements OnModuleInit {
|
|
|
1010
1213
|
return mimetype ?? 'application/octet-stream';
|
|
1011
1214
|
}
|
|
1012
1215
|
|
|
1216
|
+
/**
|
|
1217
|
+
* Widths this endpoint is willing to render. An allowlist is required, not a
|
|
1218
|
+
* range check: the endpoint is `@Public()`, and an arbitrary width means
|
|
1219
|
+
* arbitrary CPU and an unbounded number of cache entries per file.
|
|
1220
|
+
*/
|
|
1221
|
+
private async getAllowedImageWidths(): Promise<number[]> {
|
|
1222
|
+
const DEFAULT_WIDTHS = [64, 128, 320, 400, 480, 640, 896, 1200];
|
|
1223
|
+
const settings = await this.settingService.getSettingValues(
|
|
1224
|
+
'image-resize-allowed-widths',
|
|
1225
|
+
);
|
|
1226
|
+
try {
|
|
1227
|
+
const parsed = JSON.parse(settings['image-resize-allowed-widths'] ?? '');
|
|
1228
|
+
const widths = (Array.isArray(parsed) ? parsed : [])
|
|
1229
|
+
.map((w: unknown) => Math.round(Number(w)))
|
|
1230
|
+
.filter((w: number) => Number.isFinite(w) && w > 0);
|
|
1231
|
+
return widths.length ? widths : DEFAULT_WIDTHS;
|
|
1232
|
+
} catch {
|
|
1233
|
+
return DEFAULT_WIDTHS;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
/**
|
|
1238
|
+
* Resolves the requested variant against the allowlist. Anything unknown or
|
|
1239
|
+
* out of the list collapses to "no variant", which serves the original bytes
|
|
1240
|
+
* instead of failing — a stale `?w=` in cached HTML must not 500.
|
|
1241
|
+
*/
|
|
1242
|
+
private async resolveImageVariant(variant?: ImageVariantRequest) {
|
|
1243
|
+
const width = Math.round(Number(variant?.width) || 0);
|
|
1244
|
+
if (width <= 0) return null;
|
|
1245
|
+
|
|
1246
|
+
const allowed = await this.getAllowedImageWidths();
|
|
1247
|
+
if (!allowed.includes(width)) return null;
|
|
1248
|
+
|
|
1249
|
+
const format = ['webp', 'jpeg', 'png', 'avif'].includes(
|
|
1250
|
+
String(variant?.format),
|
|
1251
|
+
)
|
|
1252
|
+
? (String(variant?.format) as ImageVariantFormat)
|
|
1253
|
+
: undefined;
|
|
1254
|
+
|
|
1255
|
+
const rawQuality = Math.round(Number(variant?.quality) || 0);
|
|
1256
|
+
const quality =
|
|
1257
|
+
rawQuality >= 50 && rawQuality <= 95 ? rawQuality : 80;
|
|
1258
|
+
|
|
1259
|
+
return { width, format, quality };
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
/** Absolute path of the on-disk cache entry for a rendered variant. */
|
|
1263
|
+
private imageVariantCachePath(
|
|
1264
|
+
id: number,
|
|
1265
|
+
size: number | undefined,
|
|
1266
|
+
width: number,
|
|
1267
|
+
format: string,
|
|
1268
|
+
quality: number,
|
|
1269
|
+
) {
|
|
1270
|
+
return join(
|
|
1271
|
+
tmpdir(),
|
|
1272
|
+
'hedhog-image-variants',
|
|
1273
|
+
`${id}-${size ?? 0}-w${width}-${format}-q${quality}`,
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1013
1277
|
/**
|
|
1014
1278
|
* Serves an image by id with strong, immutable HTTP caching. The content of a
|
|
1015
1279
|
* given fileId never changes (a new upload yields a new id), so the ETag is
|
|
1016
1280
|
* derived from id+size instead of hashing the buffer, keeping it streaming.
|
|
1281
|
+
*
|
|
1282
|
+
* With a `variant` (`?w=`), the bytes are resized before being sent and the
|
|
1283
|
+
* ETag carries the variant parameters — otherwise a conditional request for
|
|
1284
|
+
* one width would get a 304 while holding the bytes of another.
|
|
1017
1285
|
*/
|
|
1018
1286
|
async serveImageById(
|
|
1019
1287
|
locale: string,
|
|
1020
1288
|
id: number,
|
|
1021
1289
|
ifNoneMatch: string | undefined,
|
|
1022
1290
|
res: any,
|
|
1291
|
+
variant?: ImageVariantRequest,
|
|
1023
1292
|
) {
|
|
1024
1293
|
const { provider, providerPath, size, mimetype, filename } =
|
|
1025
1294
|
await this.resolveFileMeta(locale, id);
|
|
@@ -1041,26 +1310,122 @@ export class FileService implements OnModuleInit {
|
|
|
1041
1310
|
);
|
|
1042
1311
|
}
|
|
1043
1312
|
|
|
1044
|
-
const
|
|
1313
|
+
const resolved = await this.resolveImageVariant(variant);
|
|
1314
|
+
const variantSuffix = resolved
|
|
1315
|
+
? `-w${resolved.width}-${resolved.format ?? 'auto'}-q${resolved.quality}`
|
|
1316
|
+
: '';
|
|
1317
|
+
const etag = `"img-${id}-${size ?? 0}${variantSuffix}"`;
|
|
1045
1318
|
if (ifNoneMatch && ifNoneMatch === etag) {
|
|
1046
1319
|
res.status(304).end();
|
|
1047
1320
|
return;
|
|
1048
1321
|
}
|
|
1049
1322
|
|
|
1050
1323
|
const ttl = await this.getCacheTtl('image-cache-ttl-seconds', 2592000);
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1324
|
+
const cacheControl = `public, max-age=${ttl}, immutable`;
|
|
1325
|
+
|
|
1326
|
+
if (resolved) {
|
|
1327
|
+
const rendered = await this.renderImageVariant(
|
|
1328
|
+
provider,
|
|
1329
|
+
providerPath,
|
|
1330
|
+
contentType,
|
|
1331
|
+
id,
|
|
1332
|
+
size,
|
|
1333
|
+
resolved,
|
|
1334
|
+
);
|
|
1335
|
+
// A null render (unsupported format, sharp failure) falls through to the
|
|
1336
|
+
// original bytes below rather than erroring the request.
|
|
1337
|
+
if (rendered) {
|
|
1338
|
+
const ext = rendered.mimetype.replace('image/', '');
|
|
1339
|
+
this.setInlineHeaders(
|
|
1340
|
+
res,
|
|
1341
|
+
`${basename(filename, extname(filename))}.${ext === 'jpeg' ? 'jpg' : ext}`,
|
|
1342
|
+
rendered.mimetype,
|
|
1343
|
+
rendered.buffer.length,
|
|
1344
|
+
cacheControl,
|
|
1345
|
+
etag,
|
|
1346
|
+
);
|
|
1347
|
+
res.end(rendered.buffer);
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
this.setInlineHeaders(res, filename, contentType, size, cacheControl, etag);
|
|
1059
1353
|
|
|
1060
1354
|
const stream = await provider.readStream(providerPath);
|
|
1061
1355
|
stream.pipe(res);
|
|
1062
1356
|
}
|
|
1063
1357
|
|
|
1358
|
+
/**
|
|
1359
|
+
* Produces (and memoizes on disk) a resized variant of a stored image.
|
|
1360
|
+
* The cache key includes the file size, so re-uploading under the same id —
|
|
1361
|
+
* which the storage layer does not normally do — cannot serve stale bytes.
|
|
1362
|
+
*/
|
|
1363
|
+
private async renderImageVariant(
|
|
1364
|
+
provider: any,
|
|
1365
|
+
providerPath: string,
|
|
1366
|
+
contentType: string,
|
|
1367
|
+
id: number,
|
|
1368
|
+
size: number | undefined,
|
|
1369
|
+
variant: { width: number; format?: ImageVariantFormat; quality: number },
|
|
1370
|
+
): Promise<{ buffer: Buffer; mimetype: string } | null> {
|
|
1371
|
+
const format =
|
|
1372
|
+
variant.format ??
|
|
1373
|
+
(contentType === 'image/png'
|
|
1374
|
+
? 'png'
|
|
1375
|
+
: contentType === 'image/webp'
|
|
1376
|
+
? 'webp'
|
|
1377
|
+
: contentType === 'image/avif'
|
|
1378
|
+
? 'avif'
|
|
1379
|
+
: 'jpeg');
|
|
1380
|
+
const cachePath = this.imageVariantCachePath(
|
|
1381
|
+
id,
|
|
1382
|
+
size,
|
|
1383
|
+
variant.width,
|
|
1384
|
+
format,
|
|
1385
|
+
variant.quality,
|
|
1386
|
+
);
|
|
1387
|
+
|
|
1388
|
+
try {
|
|
1389
|
+
const cached = await fs.readFile(cachePath);
|
|
1390
|
+
return { buffer: cached, mimetype: `image/${format}` };
|
|
1391
|
+
} catch {
|
|
1392
|
+
// Cache miss is the normal path on a cold pod; fall through and render.
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
try {
|
|
1396
|
+
const stream = await provider.readStream(providerPath);
|
|
1397
|
+
const chunks: Buffer[] = [];
|
|
1398
|
+
for await (const chunk of stream) {
|
|
1399
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
1400
|
+
}
|
|
1401
|
+
const original = Buffer.concat(chunks);
|
|
1402
|
+
|
|
1403
|
+
const rendered = await this.imageOptimizationService.resizeBuffer(
|
|
1404
|
+
original,
|
|
1405
|
+
contentType,
|
|
1406
|
+
{ width: variant.width, format: variant.format, quality: variant.quality },
|
|
1407
|
+
);
|
|
1408
|
+
if (!rendered) return null;
|
|
1409
|
+
|
|
1410
|
+
try {
|
|
1411
|
+
await fs.mkdir(dirname(cachePath), { recursive: true });
|
|
1412
|
+
await fs.writeFile(cachePath, rendered.buffer);
|
|
1413
|
+
} catch (err) {
|
|
1414
|
+
// A read-only or full tmpdir must not break serving the image.
|
|
1415
|
+
this.logger.warn(
|
|
1416
|
+
`Could not cache image variant ${cachePath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
return { buffer: rendered.buffer, mimetype: rendered.mimetype };
|
|
1421
|
+
} catch (err) {
|
|
1422
|
+
this.logger.warn(
|
|
1423
|
+
`Could not render image variant for file ${id}: ${err instanceof Error ? err.message : String(err)}. Serving the original.`,
|
|
1424
|
+
);
|
|
1425
|
+
return null;
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1064
1429
|
/**
|
|
1065
1430
|
* Serves any file by id with configurable HTTP caching (not immutable, shorter
|
|
1066
1431
|
* TTL than images). Used by the generic open endpoint's numeric-id branch.
|
|
@@ -1615,4 +1980,34 @@ export class FileService implements OnModuleInit {
|
|
|
1615
1980
|
},
|
|
1616
1981
|
);
|
|
1617
1982
|
}
|
|
1983
|
+
|
|
1984
|
+
/**
|
|
1985
|
+
* Base pública para URLs de arquivo devolvidas ao navegador.
|
|
1986
|
+
*
|
|
1987
|
+
* Não dá para derivar do `Host` da requisição: apps que falam com a API por
|
|
1988
|
+
* proxy interno (o class, via rewrite `/api` do Next) fazem o Nest enxergar o
|
|
1989
|
+
* Service do cluster, e a URL sai como `http://hub-api:3100/...` — inútil no
|
|
1990
|
+
* navegador. O setting `api-url` é a URL pública canônica da API no repo; o
|
|
1991
|
+
* `Host` fica só como fallback para ambientes sem o setting configurado.
|
|
1992
|
+
*/
|
|
1993
|
+
async resolvePublicBaseUrl(req: Request): Promise<string> {
|
|
1994
|
+
let configured = '';
|
|
1995
|
+
|
|
1996
|
+
try {
|
|
1997
|
+
const settings = await this.settingService.getSettingValues([
|
|
1998
|
+
'api-url',
|
|
1999
|
+
'url',
|
|
2000
|
+
]);
|
|
2001
|
+
const value = settings?.['api-url'] || settings?.['url'];
|
|
2002
|
+
configured = typeof value === 'string' ? value.trim() : '';
|
|
2003
|
+
} catch {
|
|
2004
|
+
configured = '';
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
if (/^https?:\/\/.+/i.test(configured)) {
|
|
2008
|
+
return configured.replace(/\/+$/, '');
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
return `${req.protocol}://${req.get('host')}`;
|
|
2012
|
+
}
|
|
1618
2013
|
}
|
|
@@ -7,6 +7,7 @@ jest.mock('sharp', () => {
|
|
|
7
7
|
webp: jest.fn(),
|
|
8
8
|
jpeg: jest.fn(),
|
|
9
9
|
png: jest.fn(),
|
|
10
|
+
avif: jest.fn(),
|
|
10
11
|
toBuffer: jest.fn(),
|
|
11
12
|
};
|
|
12
13
|
const fn: any = jest.fn(() => instance);
|
|
@@ -46,6 +47,7 @@ describe('ImageOptimizationService', () => {
|
|
|
46
47
|
inst.webp.mockReset().mockReturnValue(inst);
|
|
47
48
|
inst.jpeg.mockReset().mockReturnValue(inst);
|
|
48
49
|
inst.png.mockReset().mockReturnValue(inst);
|
|
50
|
+
inst.avif.mockReset().mockReturnValue(inst);
|
|
49
51
|
inst.toBuffer.mockReset();
|
|
50
52
|
});
|
|
51
53
|
|
|
@@ -280,4 +282,94 @@ describe('ImageOptimizationService', () => {
|
|
|
280
282
|
expect(mockSharp.__instance.jpeg).toHaveBeenCalledWith({ quality: 80 });
|
|
281
283
|
});
|
|
282
284
|
});
|
|
285
|
+
|
|
286
|
+
describe('resizeBuffer', () => {
|
|
287
|
+
it('redimensiona sem ampliar e converte para o formato pedido', async () => {
|
|
288
|
+
const { service } = makeService();
|
|
289
|
+
mockSharp.__instance.webp.mockReturnValue(mockSharp.__instance);
|
|
290
|
+
mockSharp.__instance.toBuffer.mockResolvedValue(Buffer.from('out'));
|
|
291
|
+
|
|
292
|
+
const result = await service.resizeBuffer(Buffer.from('img'), 'image/jpeg', {
|
|
293
|
+
width: 640,
|
|
294
|
+
format: 'webp',
|
|
295
|
+
quality: 82,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
expect(mockSharp.__instance.resize).toHaveBeenCalledWith({
|
|
299
|
+
width: 640,
|
|
300
|
+
fit: 'inside',
|
|
301
|
+
withoutEnlargement: true,
|
|
302
|
+
});
|
|
303
|
+
expect(mockSharp.__instance.webp).toHaveBeenCalledWith({ quality: 82 });
|
|
304
|
+
expect(result).toEqual({
|
|
305
|
+
buffer: Buffer.from('out'),
|
|
306
|
+
mimetype: 'image/webp',
|
|
307
|
+
originalname: 'image.webp',
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('mantém o formato de origem quando nenhum é pedido', async () => {
|
|
312
|
+
const { service } = makeService();
|
|
313
|
+
mockSharp.__instance.toBuffer.mockResolvedValue(Buffer.from('out'));
|
|
314
|
+
|
|
315
|
+
const result = await service.resizeBuffer(Buffer.from('img'), 'image/png', {
|
|
316
|
+
width: 320,
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
expect(mockSharp.__instance.png).toHaveBeenCalled();
|
|
320
|
+
expect(result?.mimetype).toBe('image/png');
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('não redimensiona SVG nem GIF animado', async () => {
|
|
324
|
+
const { service } = makeService();
|
|
325
|
+
|
|
326
|
+
expect(
|
|
327
|
+
await service.resizeBuffer(Buffer.from('x'), 'image/svg+xml', { width: 640 }),
|
|
328
|
+
).toBeNull();
|
|
329
|
+
expect(
|
|
330
|
+
await service.resizeBuffer(Buffer.from('x'), 'image/gif', { width: 640 }),
|
|
331
|
+
).toBeNull();
|
|
332
|
+
expect(mockSharp).not.toHaveBeenCalled();
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('retorna null para largura inválida', async () => {
|
|
336
|
+
const { service } = makeService();
|
|
337
|
+
|
|
338
|
+
expect(
|
|
339
|
+
await service.resizeBuffer(Buffer.from('x'), 'image/jpeg', { width: 0 }),
|
|
340
|
+
).toBeNull();
|
|
341
|
+
expect(
|
|
342
|
+
await service.resizeBuffer(Buffer.from('x'), 'image/jpeg', {
|
|
343
|
+
width: -10,
|
|
344
|
+
}),
|
|
345
|
+
).toBeNull();
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('retorna null quando o sharp lança, para o caller servir o original', async () => {
|
|
349
|
+
const { service } = makeService();
|
|
350
|
+
const warnSpy = jest
|
|
351
|
+
.spyOn((service as any).logger, 'warn')
|
|
352
|
+
.mockImplementation(() => undefined);
|
|
353
|
+
mockSharp.__instance.toBuffer.mockRejectedValue(new Error('sharp boom'));
|
|
354
|
+
|
|
355
|
+
const result = await service.resizeBuffer(Buffer.from('img'), 'image/jpeg', {
|
|
356
|
+
width: 640,
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
expect(result).toBeNull();
|
|
360
|
+
expect(warnSpy).toHaveBeenCalled();
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it('faz clamp da quality fora da faixa', async () => {
|
|
364
|
+
const { service } = makeService();
|
|
365
|
+
mockSharp.__instance.toBuffer.mockResolvedValue(Buffer.from('out'));
|
|
366
|
+
|
|
367
|
+
await service.resizeBuffer(Buffer.from('img'), 'image/jpeg', {
|
|
368
|
+
width: 640,
|
|
369
|
+
quality: 0,
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
expect(mockSharp.__instance.jpeg).toHaveBeenCalledWith({ quality: 80 });
|
|
373
|
+
});
|
|
374
|
+
});
|
|
283
375
|
});
|