From f00381f2b605a9414c2aabd5a1066eb0681267ed Mon Sep 17 00:00:00 2001 From: user Date: Sat, 28 Feb 2026 14:50:04 +0200 Subject: [PATCH] & so it begins --- .dockerignore | 61 + .env.example | 49 + .gitignore | 63 + AGENTS.md | 0 README.md | 5 + apps/main/.gitignore | 23 + apps/main/.npmrc | 1 + apps/main/.prettierignore | 9 + apps/main/README.md | 38 + apps/main/components.json | 16 + apps/main/package.json | 71 + apps/main/src/app.d.ts | 20 + apps/main/src/app.html | 11 + apps/main/src/demo.spec.ts | 7 + apps/main/src/hooks.server.ts | 79 + apps/main/src/lib/api.ts | 15 + apps/main/src/lib/auth.client.ts | 21 + .../src/lib/components/app-sidebar.svelte | 35 + .../lib/components/atoms/button-text.svelte | 17 + .../main/src/lib/components/atoms/icon.svelte | 11 + .../src/lib/components/atoms/title.svelte | 61 + .../molecules/google-oauth-button.svelte | 17 + .../molecules/markdown-renderer.svelte | 183 ++ .../molecules/max-width-wrapper.svelte | 11 + apps/main/src/lib/components/nav-main.svelte | 81 + apps/main/src/lib/components/nav-user.svelte | 150 ++ .../src/lib/components/team-switcher.svelte | 80 + .../ui/accordion/accordion-content.svelte | 22 + .../ui/accordion/accordion-item.svelte | 17 + .../ui/accordion/accordion-trigger.svelte | 32 + .../components/ui/accordion/accordion.svelte | 16 + .../src/lib/components/ui/accordion/index.ts | 16 + .../alert-dialog/alert-dialog-action.svelte | 18 + .../alert-dialog/alert-dialog-cancel.svelte | 18 + .../alert-dialog/alert-dialog-content.svelte | 29 + .../alert-dialog-description.svelte | 17 + .../alert-dialog/alert-dialog-footer.svelte | 20 + .../alert-dialog/alert-dialog-header.svelte | 20 + .../alert-dialog/alert-dialog-overlay.svelte | 20 + .../alert-dialog/alert-dialog-portal.svelte | 7 + .../ui/alert-dialog/alert-dialog-title.svelte | 17 + .../alert-dialog/alert-dialog-trigger.svelte | 7 + .../ui/alert-dialog/alert-dialog.svelte | 7 + .../lib/components/ui/alert-dialog/index.ts | 37 + .../ui/alert/alert-description.svelte | 23 + .../components/ui/alert/alert-title.svelte | 20 + .../src/lib/components/ui/alert/alert.svelte | 44 + .../main/src/lib/components/ui/alert/index.ts | 14 + .../ui/aspect-ratio/aspect-ratio.svelte | 7 + .../lib/components/ui/aspect-ratio/index.ts | 3 + .../ui/avatar/avatar-fallback.svelte | 17 + .../components/ui/avatar/avatar-image.svelte | 17 + .../lib/components/ui/avatar/avatar.svelte | 19 + .../src/lib/components/ui/avatar/index.ts | 13 + .../src/lib/components/ui/badge/badge.svelte | 50 + .../main/src/lib/components/ui/badge/index.ts | 2 + .../ui/breadcrumb/breadcrumb-ellipsis.svelte | 23 + .../ui/breadcrumb/breadcrumb-item.svelte | 20 + .../ui/breadcrumb/breadcrumb-link.svelte | 31 + .../ui/breadcrumb/breadcrumb-list.svelte | 23 + .../ui/breadcrumb/breadcrumb-page.svelte | 23 + .../ui/breadcrumb/breadcrumb-separator.svelte | 27 + .../ui/breadcrumb/breadcrumb.svelte | 21 + .../src/lib/components/ui/breadcrumb/index.ts | 25 + .../button-group-separator.svelte | 20 + .../ui/button-group/button-group-text.svelte | 30 + .../ui/button-group/button-group.svelte | 46 + .../lib/components/ui/button-group/index.ts | 13 + .../lib/components/ui/button/button.svelte | 82 + .../src/lib/components/ui/button/index.ts | 17 + .../ui/calendar/calendar-caption.svelte | 76 + .../ui/calendar/calendar-cell.svelte | 19 + .../ui/calendar/calendar-day.svelte | 35 + .../ui/calendar/calendar-grid-body.svelte | 12 + .../ui/calendar/calendar-grid-head.svelte | 12 + .../ui/calendar/calendar-grid-row.svelte | 12 + .../ui/calendar/calendar-grid.svelte | 16 + .../ui/calendar/calendar-head-cell.svelte | 19 + .../ui/calendar/calendar-header.svelte | 19 + .../ui/calendar/calendar-heading.svelte | 16 + .../ui/calendar/calendar-month-select.svelte | 44 + .../ui/calendar/calendar-month.svelte | 15 + .../ui/calendar/calendar-months.svelte | 19 + .../ui/calendar/calendar-nav.svelte | 19 + .../ui/calendar/calendar-next-button.svelte | 31 + .../ui/calendar/calendar-prev-button.svelte | 31 + .../ui/calendar/calendar-year-select.svelte | 43 + .../components/ui/calendar/calendar.svelte | 115 ++ .../src/lib/components/ui/calendar/index.ts | 40 + .../lib/components/ui/card/card-action.svelte | 20 + .../components/ui/card/card-content.svelte | 15 + .../ui/card/card-description.svelte | 20 + .../lib/components/ui/card/card-footer.svelte | 20 + .../lib/components/ui/card/card-header.svelte | 23 + .../lib/components/ui/card/card-title.svelte | 20 + .../src/lib/components/ui/card/card.svelte | 23 + apps/main/src/lib/components/ui/card/index.ts | 25 + .../ui/carousel/carousel-content.svelte | 43 + .../ui/carousel/carousel-item.svelte | 30 + .../ui/carousel/carousel-next.svelte | 38 + .../ui/carousel/carousel-previous.svelte | 38 + .../components/ui/carousel/carousel.svelte | 93 + .../src/lib/components/ui/carousel/context.ts | 58 + .../src/lib/components/ui/carousel/index.ts | 19 + .../ui/chart/chart-container.svelte | 80 + .../components/ui/chart/chart-style.svelte | 37 + .../components/ui/chart/chart-tooltip.svelte | 159 ++ .../lib/components/ui/chart/chart-utils.ts | 66 + .../main/src/lib/components/ui/chart/index.ts | 6 + .../components/ui/checkbox/checkbox.svelte | 36 + .../src/lib/components/ui/checkbox/index.ts | 6 + .../ui/collapsible/collapsible-content.svelte | 7 + .../ui/collapsible/collapsible-trigger.svelte | 7 + .../ui/collapsible/collapsible.svelte | 11 + .../lib/components/ui/collapsible/index.ts | 13 + .../ui/command/command-dialog.svelte | 40 + .../ui/command/command-empty.svelte | 17 + .../ui/command/command-group.svelte | 32 + .../ui/command/command-input.svelte | 26 + .../components/ui/command/command-item.svelte | 20 + .../ui/command/command-link-item.svelte | 20 + .../components/ui/command/command-list.svelte | 17 + .../ui/command/command-loading.svelte | 7 + .../ui/command/command-separator.svelte | 17 + .../ui/command/command-shortcut.svelte | 20 + .../lib/components/ui/command/command.svelte | 28 + .../src/lib/components/ui/command/index.ts | 37 + .../context-menu-checkbox-item.svelte | 40 + .../context-menu/context-menu-content.svelte | 28 + .../context-menu-group-heading.svelte | 21 + .../ui/context-menu/context-menu-group.svelte | 7 + .../ui/context-menu/context-menu-item.svelte | 27 + .../ui/context-menu/context-menu-label.svelte | 24 + .../context-menu/context-menu-portal.svelte | 7 + .../context-menu-radio-group.svelte | 16 + .../context-menu-radio-item.svelte | 33 + .../context-menu-separator.svelte | 17 + .../context-menu/context-menu-shortcut.svelte | 20 + .../context-menu-sub-content.svelte | 20 + .../context-menu-sub-trigger.svelte | 29 + .../ui/context-menu/context-menu-sub.svelte | 7 + .../context-menu/context-menu-trigger.svelte | 7 + .../ui/context-menu/context-menu.svelte | 7 + .../lib/components/ui/context-menu/index.ts | 52 + .../ui/data-table/data-table.svelte.ts | 142 ++ .../ui/data-table/flex-render.svelte | 40 + .../src/lib/components/ui/data-table/index.ts | 3 + .../ui/data-table/render-helpers.ts | 111 ++ .../components/ui/dialog/dialog-close.svelte | 7 + .../ui/dialog/dialog-content.svelte | 45 + .../ui/dialog/dialog-description.svelte | 17 + .../components/ui/dialog/dialog-footer.svelte | 20 + .../components/ui/dialog/dialog-header.svelte | 20 + .../ui/dialog/dialog-overlay.svelte | 20 + .../components/ui/dialog/dialog-portal.svelte | 7 + .../components/ui/dialog/dialog-title.svelte | 17 + .../ui/dialog/dialog-trigger.svelte | 7 + .../lib/components/ui/dialog/dialog.svelte | 7 + .../src/lib/components/ui/dialog/index.ts | 34 + .../components/ui/drawer/drawer-close.svelte | 7 + .../ui/drawer/drawer-content.svelte | 40 + .../ui/drawer/drawer-description.svelte | 17 + .../components/ui/drawer/drawer-footer.svelte | 20 + .../components/ui/drawer/drawer-header.svelte | 20 + .../components/ui/drawer/drawer-nested.svelte | 12 + .../ui/drawer/drawer-overlay.svelte | 20 + .../components/ui/drawer/drawer-portal.svelte | 7 + .../components/ui/drawer/drawer-title.svelte | 17 + .../ui/drawer/drawer-trigger.svelte | 7 + .../lib/components/ui/drawer/drawer.svelte | 12 + .../src/lib/components/ui/drawer/index.ts | 38 + .../dropdown-menu-checkbox-group.svelte | 16 + .../dropdown-menu-checkbox-item.svelte | 43 + .../dropdown-menu-content.svelte | 29 + .../dropdown-menu-group-heading.svelte | 22 + .../dropdown-menu/dropdown-menu-group.svelte | 7 + .../dropdown-menu/dropdown-menu-item.svelte | 27 + .../dropdown-menu/dropdown-menu-label.svelte | 24 + .../dropdown-menu/dropdown-menu-portal.svelte | 7 + .../dropdown-menu-radio-group.svelte | 16 + .../dropdown-menu-radio-item.svelte | 33 + .../dropdown-menu-separator.svelte | 17 + .../dropdown-menu-shortcut.svelte | 20 + .../dropdown-menu-sub-content.svelte | 20 + .../dropdown-menu-sub-trigger.svelte | 29 + .../ui/dropdown-menu/dropdown-menu-sub.svelte | 7 + .../dropdown-menu-trigger.svelte | 7 + .../ui/dropdown-menu/dropdown-menu.svelte | 7 + .../lib/components/ui/dropdown-menu/index.ts | 54 + .../components/ui/empty/empty-content.svelte | 23 + .../ui/empty/empty-description.svelte | 23 + .../components/ui/empty/empty-header.svelte | 20 + .../components/ui/empty/empty-media.svelte | 41 + .../components/ui/empty/empty-title.svelte | 20 + .../src/lib/components/ui/empty/empty.svelte | 23 + .../main/src/lib/components/ui/empty/index.ts | 22 + .../components/ui/field/field-content.svelte | 20 + .../ui/field/field-description.svelte | 25 + .../components/ui/field/field-error.svelte | 58 + .../components/ui/field/field-group.svelte | 23 + .../components/ui/field/field-label.svelte | 26 + .../components/ui/field/field-legend.svelte | 29 + .../ui/field/field-separator.svelte | 38 + .../lib/components/ui/field/field-set.svelte | 24 + .../components/ui/field/field-title.svelte | 23 + .../src/lib/components/ui/field/field.svelte | 53 + .../main/src/lib/components/ui/field/index.ts | 33 + .../lib/components/ui/form/form-button.svelte | 7 + .../ui/form/form-description.svelte | 17 + .../ui/form/form-element-field.svelte | 24 + .../ui/form/form-field-errors.svelte | 30 + .../lib/components/ui/form/form-field.svelte | 29 + .../components/ui/form/form-fieldset.svelte | 15 + .../lib/components/ui/form/form-label.svelte | 24 + .../lib/components/ui/form/form-legend.svelte | 16 + apps/main/src/lib/components/ui/form/index.ts | 33 + .../ui/hover-card/hover-card-content.svelte | 31 + .../ui/hover-card/hover-card-portal.svelte | 7 + .../ui/hover-card/hover-card-trigger.svelte | 7 + .../ui/hover-card/hover-card.svelte | 7 + .../src/lib/components/ui/hover-card/index.ts | 15 + .../lib/components/ui/input-group/index.ts | 22 + .../ui/input-group/input-group-addon.svelte | 55 + .../ui/input-group/input-group-button.svelte | 49 + .../ui/input-group/input-group-input.svelte | 23 + .../ui/input-group/input-group-text.svelte | 22 + .../input-group/input-group-textarea.svelte | 23 + .../ui/input-group/input-group.svelte | 38 + .../src/lib/components/ui/input-otp/index.ts | 15 + .../ui/input-otp/input-otp-group.svelte | 20 + .../ui/input-otp/input-otp-separator.svelte | 19 + .../ui/input-otp/input-otp-slot.svelte | 31 + .../components/ui/input-otp/input-otp.svelte | 22 + .../main/src/lib/components/ui/input/index.ts | 7 + .../src/lib/components/ui/input/input.svelte | 52 + apps/main/src/lib/components/ui/item/index.ts | 34 + .../components/ui/item/item-actions.svelte | 20 + .../components/ui/item/item-content.svelte | 20 + .../ui/item/item-description.svelte | 24 + .../lib/components/ui/item/item-footer.svelte | 20 + .../lib/components/ui/item/item-group.svelte | 21 + .../lib/components/ui/item/item-header.svelte | 20 + .../lib/components/ui/item/item-media.svelte | 42 + .../components/ui/item/item-separator.svelte | 19 + .../lib/components/ui/item/item-title.svelte | 20 + .../src/lib/components/ui/item/item.svelte | 60 + apps/main/src/lib/components/ui/kbd/index.ts | 10 + .../lib/components/ui/kbd/kbd-group.svelte | 20 + .../main/src/lib/components/ui/kbd/kbd.svelte | 25 + .../main/src/lib/components/ui/label/index.ts | 7 + .../src/lib/components/ui/label/label.svelte | 20 + .../src/lib/components/ui/menubar/index.ts | 55 + .../ui/menubar/menubar-checkbox-item.svelte | 43 + .../ui/menubar/menubar-content.svelte | 35 + .../ui/menubar/menubar-group-heading.svelte | 22 + .../ui/menubar/menubar-group.svelte | 12 + .../components/ui/menubar/menubar-item.svelte | 27 + .../ui/menubar/menubar-label.svelte | 25 + .../components/ui/menubar/menubar-menu.svelte | 7 + .../ui/menubar/menubar-portal.svelte | 7 + .../ui/menubar/menubar-radio-group.svelte | 11 + .../ui/menubar/menubar-radio-item.svelte | 33 + .../ui/menubar/menubar-separator.svelte | 17 + .../ui/menubar/menubar-shortcut.svelte | 20 + .../ui/menubar/menubar-sub-content.svelte | 20 + .../ui/menubar/menubar-sub-trigger.svelte | 29 + .../components/ui/menubar/menubar-sub.svelte | 7 + .../ui/menubar/menubar-trigger.svelte | 20 + .../lib/components/ui/menubar/menubar.svelte | 20 + .../lib/components/ui/native-select/index.ts | 12 + .../native-select-opt-group.svelte | 14 + .../native-select/native-select-option.svelte | 14 + .../ui/native-select/native-select.svelte | 38 + .../components/ui/navigation-menu/index.ts | 28 + .../navigation-menu-content.svelte | 21 + .../navigation-menu-indicator.svelte | 22 + .../navigation-menu-item.svelte | 17 + .../navigation-menu-link.svelte | 20 + .../navigation-menu-list.svelte | 17 + .../navigation-menu-trigger.svelte | 34 + .../navigation-menu-viewport.svelte | 22 + .../ui/navigation-menu/navigation-menu.svelte | 32 + .../src/lib/components/ui/pagination/index.ts | 31 + .../ui/pagination/pagination-content.svelte | 20 + .../ui/pagination/pagination-ellipsis.svelte | 22 + .../ui/pagination/pagination-item.svelte | 14 + .../ui/pagination/pagination-link.svelte | 39 + .../pagination/pagination-next-button.svelte | 33 + .../ui/pagination/pagination-next.svelte | 29 + .../pagination/pagination-prev-button.svelte | 33 + .../ui/pagination/pagination-previous.svelte | 29 + .../ui/pagination/pagination.svelte | 28 + .../src/lib/components/ui/popover/index.ts | 19 + .../ui/popover/popover-close.svelte | 7 + .../ui/popover/popover-content.svelte | 31 + .../ui/popover/popover-portal.svelte | 7 + .../ui/popover/popover-trigger.svelte | 17 + .../lib/components/ui/popover/popover.svelte | 7 + .../src/lib/components/ui/progress/index.ts | 7 + .../components/ui/progress/progress.svelte | 27 + .../lib/components/ui/radio-group/index.ts | 10 + .../ui/radio-group/radio-group-item.svelte | 31 + .../ui/radio-group/radio-group.svelte | 19 + .../lib/components/ui/range-calendar/index.ts | 40 + .../range-calendar-caption.svelte | 76 + .../range-calendar/range-calendar-cell.svelte | 19 + .../range-calendar/range-calendar-day.svelte | 39 + .../range-calendar-grid-body.svelte | 7 + .../range-calendar-grid-head.svelte | 7 + .../range-calendar-grid-row.svelte | 12 + .../range-calendar/range-calendar-grid.svelte | 16 + .../range-calendar-head-cell.svelte | 19 + .../range-calendar-header.svelte | 19 + .../range-calendar-heading.svelte | 16 + .../range-calendar-month-select.svelte | 44 + .../range-calendar-month.svelte | 15 + .../range-calendar-months.svelte | 19 + .../range-calendar/range-calendar-nav.svelte | 19 + .../range-calendar-next-button.svelte | 31 + .../range-calendar-prev-button.svelte | 31 + .../range-calendar-year-select.svelte | 43 + .../ui/range-calendar/range-calendar.svelte | 112 ++ .../src/lib/components/ui/resizable/index.ts | 13 + .../ui/resizable/resizable-handle.svelte | 30 + .../ui/resizable/resizable-pane-group.svelte | 20 + .../lib/components/ui/scroll-area/index.ts | 10 + .../scroll-area/scroll-area-scrollbar.svelte | 31 + .../ui/scroll-area/scroll-area.svelte | 43 + .../src/lib/components/ui/select/index.ts | 37 + .../ui/select/select-content.svelte | 45 + .../ui/select/select-group-heading.svelte | 21 + .../components/ui/select/select-group.svelte | 7 + .../components/ui/select/select-item.svelte | 38 + .../components/ui/select/select-label.svelte | 20 + .../components/ui/select/select-portal.svelte | 7 + .../select/select-scroll-down-button.svelte | 20 + .../ui/select/select-scroll-up-button.svelte | 20 + .../ui/select/select-separator.svelte | 18 + .../ui/select/select-trigger.svelte | 29 + .../lib/components/ui/select/select.svelte | 11 + .../src/lib/components/ui/separator/index.ts | 7 + .../components/ui/separator/separator.svelte | 21 + .../main/src/lib/components/ui/sheet/index.ts | 34 + .../components/ui/sheet/sheet-close.svelte | 7 + .../components/ui/sheet/sheet-content.svelte | 60 + .../ui/sheet/sheet-description.svelte | 17 + .../components/ui/sheet/sheet-footer.svelte | 20 + .../components/ui/sheet/sheet-header.svelte | 20 + .../components/ui/sheet/sheet-overlay.svelte | 20 + .../components/ui/sheet/sheet-portal.svelte | 7 + .../components/ui/sheet/sheet-title.svelte | 17 + .../components/ui/sheet/sheet-trigger.svelte | 7 + .../src/lib/components/ui/sheet/sheet.svelte | 7 + .../lib/components/ui/sidebar/constants.ts | 6 + .../components/ui/sidebar/context.svelte.ts | 81 + .../src/lib/components/ui/sidebar/index.ts | 75 + .../ui/sidebar/sidebar-content.svelte | 24 + .../ui/sidebar/sidebar-footer.svelte | 21 + .../ui/sidebar/sidebar-group-action.svelte | 36 + .../ui/sidebar/sidebar-group-content.svelte | 21 + .../ui/sidebar/sidebar-group-label.svelte | 34 + .../ui/sidebar/sidebar-group.svelte | 21 + .../ui/sidebar/sidebar-header.svelte | 21 + .../ui/sidebar/sidebar-input.svelte | 21 + .../ui/sidebar/sidebar-inset.svelte | 24 + .../ui/sidebar/sidebar-menu-action.svelte | 43 + .../ui/sidebar/sidebar-menu-badge.svelte | 29 + .../ui/sidebar/sidebar-menu-button.svelte | 103 ++ .../ui/sidebar/sidebar-menu-item.svelte | 21 + .../ui/sidebar/sidebar-menu-skeleton.svelte | 36 + .../ui/sidebar/sidebar-menu-sub-button.svelte | 43 + .../ui/sidebar/sidebar-menu-sub-item.svelte | 21 + .../ui/sidebar/sidebar-menu-sub.svelte | 25 + .../components/ui/sidebar/sidebar-menu.svelte | 21 + .../ui/sidebar/sidebar-provider.svelte | 53 + .../components/ui/sidebar/sidebar-rail.svelte | 36 + .../ui/sidebar/sidebar-separator.svelte | 19 + .../ui/sidebar/sidebar-trigger.svelte | 35 + .../lib/components/ui/sidebar/sidebar.svelte | 104 ++ .../src/lib/components/ui/skeleton/index.ts | 7 + .../components/ui/skeleton/skeleton.svelte | 17 + .../src/lib/components/ui/slider/index.ts | 7 + .../lib/components/ui/slider/slider.svelte | 52 + .../src/lib/components/ui/sonner/index.ts | 1 + .../lib/components/ui/sonner/sonner.svelte | 34 + .../src/lib/components/ui/spinner/index.ts | 1 + .../lib/components/ui/spinner/spinner.svelte | 14 + .../src/lib/components/ui/switch/index.ts | 7 + .../lib/components/ui/switch/switch.svelte | 29 + .../main/src/lib/components/ui/table/index.ts | 28 + .../lib/components/ui/table/table-body.svelte | 20 + .../components/ui/table/table-caption.svelte | 20 + .../lib/components/ui/table/table-cell.svelte | 23 + .../components/ui/table/table-footer.svelte | 20 + .../lib/components/ui/table/table-head.svelte | 23 + .../components/ui/table/table-header.svelte | 20 + .../lib/components/ui/table/table-row.svelte | 23 + .../src/lib/components/ui/table/table.svelte | 22 + apps/main/src/lib/components/ui/tabs/index.ts | 16 + .../components/ui/tabs/tabs-content.svelte | 17 + .../lib/components/ui/tabs/tabs-list.svelte | 20 + .../components/ui/tabs/tabs-trigger.svelte | 20 + .../src/lib/components/ui/tabs/tabs.svelte | 19 + .../src/lib/components/ui/textarea/index.ts | 7 + .../components/ui/textarea/textarea.svelte | 23 + .../lib/components/ui/toggle-group/index.ts | 10 + .../ui/toggle-group/toggle-group-item.svelte | 35 + .../ui/toggle-group/toggle-group.svelte | 59 + .../src/lib/components/ui/toggle/index.ts | 13 + .../lib/components/ui/toggle/toggle.svelte | 52 + .../src/lib/components/ui/tooltip/index.ts | 19 + .../ui/tooltip/tooltip-content.svelte | 52 + .../ui/tooltip/tooltip-portal.svelte | 7 + .../ui/tooltip/tooltip-provider.svelte | 7 + .../ui/tooltip/tooltip-trigger.svelte | 7 + .../lib/components/ui/tooltip/tooltip.svelte | 7 + apps/main/src/lib/core/constants.ts | 50 + apps/main/src/lib/currency.utils.ts | 5 + .../lib/domains/account/account.vm.svelte.ts | 157 ++ .../account/sessions/sessions-card.svelte | 182 ++ .../account/sessions/sessions.vm.svelte.ts | 209 +++ .../notifications/notification.vm.svelte.ts | 536 ++++++ .../notifications/notifications-table.svelte | 566 ++++++ .../lib/domains/security/auth.vm.svelte.ts | 233 +++ .../domains/security/email-login-form.svelte | 154 ++ .../security/magic-link-verification.svelte | 228 +++ apps/main/src/lib/domains/todo/list.svelte | 0 apps/main/src/lib/global.stores.ts | 14 + apps/main/src/lib/hooks/is-mobile.svelte.ts | 9 + apps/main/src/lib/make-client.ts | 21 + apps/main/src/lib/utils.ts | 13 + apps/main/src/routes/(main)/+layout.server.ts | 13 + apps/main/src/routes/(main)/+layout.svelte | 58 + apps/main/src/routes/(main)/+page.server.ts | 6 + apps/main/src/routes/(main)/+page.svelte | 18 + .../src/routes/(main)/account/+layout.svelte | 134 ++ .../src/routes/(main)/account/+page.svelte | 247 +++ .../(main)/account/verify-email/+page.svelte | 240 +++ .../src/routes/(main)/dashboard/+page.svelte | 18 + .../routes/(main)/notifications/+page.svelte | 27 + .../src/routes/(main)/users/+page.server.ts | 13 + .../main/src/routes/(main)/users/+page.svelte | 73 + apps/main/src/routes/+layout.svelte | 36 + .../src/routes/api/debug/users/+server.ts | 92 + .../src/routes/api/v1/[...paths]/+server.ts | 93 + .../src/routes/auth/2fa/+layout.server.ts | 14 + apps/main/src/routes/auth/2fa/+page.svelte | 285 ++++ .../src/routes/auth/login/+page.server.ts | 13 + apps/main/src/routes/auth/login/+page.svelte | 77 + .../src/routes/auth/magic-link/+page.svelte | 11 + apps/main/src/routes/layout.css | 196 +++ apps/main/static/favicon.png | Bin 0 -> 303316 bytes apps/main/static/fonts/manrope-variable.ttf | Bin 0 -> 164936 bytes apps/main/static/images/avatar.png | Bin 0 -> 2011 bytes apps/main/static/robots.txt | 3 + apps/main/svelte.config.js | 18 + apps/main/tsconfig.json | 20 + apps/main/vite.config.ts | 35 + apps/processor/.gitignore | 2 + apps/processor/README.md | 11 + apps/processor/package.json | 13 + apps/processor/src/index.ts | 9 + apps/processor/tsconfig.json | 7 + bun.lock | 1513 +++++++++++++++++ dev/README.md | 42 + dev/clickhouse-cluster.xml | 75 + dev/clickhouse-config.xml | 1142 +++++++++++++ dev/clickhouse-custom-function.xml | 21 + dev/clickhouse-users.xml | 123 ++ dev/docker-compose.dev.yaml | 218 +++ dev/otel-collector-config.yaml | 124 ++ dev/otel-collector-opamp-config.yaml | 1 + dockerfiles/main.Dockerfile | 27 + dockerfiles/migrator.Dockerfile | 17 + dockerfiles/processor.Dockerfile | 26 + package.json | 42 + packages/db/drizzle.config.ts | 13 + packages/db/index.ts | 11 + .../migrations/0000_woozy_mother_askani.sql | 119 ++ .../db/migrations/meta/0000_snapshot.json | 655 +++++++ packages/db/migrations/meta/_journal.json | 13 + packages/db/package.json | 27 + packages/db/schema/auth.schema.ts | 52 + packages/db/schema/better.auth.schema.ts | 75 + packages/db/schema/general.schema.ts | 49 + packages/db/schema/index.ts | 3 + packages/logger/client.ts | 222 +++ packages/logger/index.ts | 143 ++ packages/logger/package.json | 18 + packages/logger/tsconfig.json | 5 + packages/logic/core/array.utils.ts | 7 + packages/logic/core/data/countries.ts | 264 +++ packages/logic/core/data/phonecc.ts | 1227 +++++++++++++ packages/logic/core/date.utils.ts | 83 + packages/logic/core/error.ts | 8 + packages/logic/core/flow.execution.context.ts | 5 + packages/logic/core/hash.utils.ts | 31 + packages/logic/core/hono.helpers.ts | 9 + packages/logic/core/pagination.utils.ts | 12 + packages/logic/core/rate.limiter.ts | 40 + packages/logic/core/settings.ts | 1 + packages/logic/core/string.utils/index.ts | 106 ++ .../core/string.utils/sequence.matcher.ts | 555 ++++++ packages/logic/domains/2fa/controller.ts | 349 ++++ packages/logic/domains/2fa/data.ts | 48 + packages/logic/domains/2fa/errors.ts | 180 ++ packages/logic/domains/2fa/repository.ts | 554 ++++++ packages/logic/domains/2fa/router.ts | 170 ++ .../logic/domains/2fa/sensitive-actions.ts | 43 + packages/logic/domains/auth/config.base.ts | 205 +++ packages/logic/domains/auth/controller.ts | 148 ++ packages/logic/domains/auth/errors.ts | 59 + .../logic/domains/notifications/controller.ts | 96 ++ packages/logic/domains/notifications/data.ts | 102 ++ .../logic/domains/notifications/errors.ts | 78 + .../logic/domains/notifications/repository.ts | 384 +++++ .../logic/domains/notifications/router.ts | 160 ++ .../logic/domains/user/account.repository.ts | 213 +++ packages/logic/domains/user/controller.ts | 55 + packages/logic/domains/user/data.ts | 159 ++ packages/logic/domains/user/errors.ts | 77 + packages/logic/domains/user/repository.ts | 289 ++++ packages/logic/domains/user/router.ts | 165 ++ packages/logic/package.json | 40 + packages/logic/tsconfig.json | 16 + packages/redis/index.ts | 18 + packages/redis/package.json | 15 + packages/result/index.ts | 81 + packages/result/package.json | 9 + packages/settings/index.ts | 178 ++ packages/settings/package.json | 15 + scripts/migrate.sh | 7 + scripts/populate.env.sh | 15 + scripts/prod.start.sh | 14 + tsconfig.json | 5 + turbo.json | 24 + 536 files changed, 26294 insertions(+) create mode 100644 .dockerignore create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 README.md create mode 100644 apps/main/.gitignore create mode 100644 apps/main/.npmrc create mode 100644 apps/main/.prettierignore create mode 100644 apps/main/README.md create mode 100644 apps/main/components.json create mode 100644 apps/main/package.json create mode 100644 apps/main/src/app.d.ts create mode 100644 apps/main/src/app.html create mode 100644 apps/main/src/demo.spec.ts create mode 100644 apps/main/src/hooks.server.ts create mode 100644 apps/main/src/lib/api.ts create mode 100644 apps/main/src/lib/auth.client.ts create mode 100644 apps/main/src/lib/components/app-sidebar.svelte create mode 100644 apps/main/src/lib/components/atoms/button-text.svelte create mode 100644 apps/main/src/lib/components/atoms/icon.svelte create mode 100644 apps/main/src/lib/components/atoms/title.svelte create mode 100644 apps/main/src/lib/components/molecules/google-oauth-button.svelte create mode 100644 apps/main/src/lib/components/molecules/markdown-renderer.svelte create mode 100644 apps/main/src/lib/components/molecules/max-width-wrapper.svelte create mode 100644 apps/main/src/lib/components/nav-main.svelte create mode 100644 apps/main/src/lib/components/nav-user.svelte create mode 100644 apps/main/src/lib/components/team-switcher.svelte create mode 100644 apps/main/src/lib/components/ui/accordion/accordion-content.svelte create mode 100644 apps/main/src/lib/components/ui/accordion/accordion-item.svelte create mode 100644 apps/main/src/lib/components/ui/accordion/accordion-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/accordion/accordion.svelte create mode 100644 apps/main/src/lib/components/ui/accordion/index.ts create mode 100644 apps/main/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte create mode 100644 apps/main/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte create mode 100644 apps/main/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte create mode 100644 apps/main/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte create mode 100644 apps/main/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte create mode 100644 apps/main/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte create mode 100644 apps/main/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte create mode 100644 apps/main/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte create mode 100644 apps/main/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte create mode 100644 apps/main/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/alert-dialog/alert-dialog.svelte create mode 100644 apps/main/src/lib/components/ui/alert-dialog/index.ts create mode 100644 apps/main/src/lib/components/ui/alert/alert-description.svelte create mode 100644 apps/main/src/lib/components/ui/alert/alert-title.svelte create mode 100644 apps/main/src/lib/components/ui/alert/alert.svelte create mode 100644 apps/main/src/lib/components/ui/alert/index.ts create mode 100644 apps/main/src/lib/components/ui/aspect-ratio/aspect-ratio.svelte create mode 100644 apps/main/src/lib/components/ui/aspect-ratio/index.ts create mode 100644 apps/main/src/lib/components/ui/avatar/avatar-fallback.svelte create mode 100644 apps/main/src/lib/components/ui/avatar/avatar-image.svelte create mode 100644 apps/main/src/lib/components/ui/avatar/avatar.svelte create mode 100644 apps/main/src/lib/components/ui/avatar/index.ts create mode 100644 apps/main/src/lib/components/ui/badge/badge.svelte create mode 100644 apps/main/src/lib/components/ui/badge/index.ts create mode 100644 apps/main/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte create mode 100644 apps/main/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte create mode 100644 apps/main/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte create mode 100644 apps/main/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte create mode 100644 apps/main/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte create mode 100644 apps/main/src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte create mode 100644 apps/main/src/lib/components/ui/breadcrumb/breadcrumb.svelte create mode 100644 apps/main/src/lib/components/ui/breadcrumb/index.ts create mode 100644 apps/main/src/lib/components/ui/button-group/button-group-separator.svelte create mode 100644 apps/main/src/lib/components/ui/button-group/button-group-text.svelte create mode 100644 apps/main/src/lib/components/ui/button-group/button-group.svelte create mode 100644 apps/main/src/lib/components/ui/button-group/index.ts create mode 100644 apps/main/src/lib/components/ui/button/button.svelte create mode 100644 apps/main/src/lib/components/ui/button/index.ts create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-caption.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-cell.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-day.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-grid-body.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-grid-head.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-grid-row.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-grid.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-head-cell.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-header.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-heading.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-month-select.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-month.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-months.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-nav.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-next-button.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-prev-button.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-year-select.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/index.ts create mode 100644 apps/main/src/lib/components/ui/card/card-action.svelte create mode 100644 apps/main/src/lib/components/ui/card/card-content.svelte create mode 100644 apps/main/src/lib/components/ui/card/card-description.svelte create mode 100644 apps/main/src/lib/components/ui/card/card-footer.svelte create mode 100644 apps/main/src/lib/components/ui/card/card-header.svelte create mode 100644 apps/main/src/lib/components/ui/card/card-title.svelte create mode 100644 apps/main/src/lib/components/ui/card/card.svelte create mode 100644 apps/main/src/lib/components/ui/card/index.ts create mode 100644 apps/main/src/lib/components/ui/carousel/carousel-content.svelte create mode 100644 apps/main/src/lib/components/ui/carousel/carousel-item.svelte create mode 100644 apps/main/src/lib/components/ui/carousel/carousel-next.svelte create mode 100644 apps/main/src/lib/components/ui/carousel/carousel-previous.svelte create mode 100644 apps/main/src/lib/components/ui/carousel/carousel.svelte create mode 100644 apps/main/src/lib/components/ui/carousel/context.ts create mode 100644 apps/main/src/lib/components/ui/carousel/index.ts create mode 100644 apps/main/src/lib/components/ui/chart/chart-container.svelte create mode 100644 apps/main/src/lib/components/ui/chart/chart-style.svelte create mode 100644 apps/main/src/lib/components/ui/chart/chart-tooltip.svelte create mode 100644 apps/main/src/lib/components/ui/chart/chart-utils.ts create mode 100644 apps/main/src/lib/components/ui/chart/index.ts create mode 100644 apps/main/src/lib/components/ui/checkbox/checkbox.svelte create mode 100644 apps/main/src/lib/components/ui/checkbox/index.ts create mode 100644 apps/main/src/lib/components/ui/collapsible/collapsible-content.svelte create mode 100644 apps/main/src/lib/components/ui/collapsible/collapsible-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/collapsible/collapsible.svelte create mode 100644 apps/main/src/lib/components/ui/collapsible/index.ts create mode 100644 apps/main/src/lib/components/ui/command/command-dialog.svelte create mode 100644 apps/main/src/lib/components/ui/command/command-empty.svelte create mode 100644 apps/main/src/lib/components/ui/command/command-group.svelte create mode 100644 apps/main/src/lib/components/ui/command/command-input.svelte create mode 100644 apps/main/src/lib/components/ui/command/command-item.svelte create mode 100644 apps/main/src/lib/components/ui/command/command-link-item.svelte create mode 100644 apps/main/src/lib/components/ui/command/command-list.svelte create mode 100644 apps/main/src/lib/components/ui/command/command-loading.svelte create mode 100644 apps/main/src/lib/components/ui/command/command-separator.svelte create mode 100644 apps/main/src/lib/components/ui/command/command-shortcut.svelte create mode 100644 apps/main/src/lib/components/ui/command/command.svelte create mode 100644 apps/main/src/lib/components/ui/command/index.ts create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-checkbox-item.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-content.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-group-heading.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-group.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-item.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-label.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-portal.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-radio-group.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-radio-item.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-separator.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-shortcut.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-sub-content.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-sub-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-sub.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/index.ts create mode 100644 apps/main/src/lib/components/ui/data-table/data-table.svelte.ts create mode 100644 apps/main/src/lib/components/ui/data-table/flex-render.svelte create mode 100644 apps/main/src/lib/components/ui/data-table/index.ts create mode 100644 apps/main/src/lib/components/ui/data-table/render-helpers.ts create mode 100644 apps/main/src/lib/components/ui/dialog/dialog-close.svelte create mode 100644 apps/main/src/lib/components/ui/dialog/dialog-content.svelte create mode 100644 apps/main/src/lib/components/ui/dialog/dialog-description.svelte create mode 100644 apps/main/src/lib/components/ui/dialog/dialog-footer.svelte create mode 100644 apps/main/src/lib/components/ui/dialog/dialog-header.svelte create mode 100644 apps/main/src/lib/components/ui/dialog/dialog-overlay.svelte create mode 100644 apps/main/src/lib/components/ui/dialog/dialog-portal.svelte create mode 100644 apps/main/src/lib/components/ui/dialog/dialog-title.svelte create mode 100644 apps/main/src/lib/components/ui/dialog/dialog-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/dialog/dialog.svelte create mode 100644 apps/main/src/lib/components/ui/dialog/index.ts create mode 100644 apps/main/src/lib/components/ui/drawer/drawer-close.svelte create mode 100644 apps/main/src/lib/components/ui/drawer/drawer-content.svelte create mode 100644 apps/main/src/lib/components/ui/drawer/drawer-description.svelte create mode 100644 apps/main/src/lib/components/ui/drawer/drawer-footer.svelte create mode 100644 apps/main/src/lib/components/ui/drawer/drawer-header.svelte create mode 100644 apps/main/src/lib/components/ui/drawer/drawer-nested.svelte create mode 100644 apps/main/src/lib/components/ui/drawer/drawer-overlay.svelte create mode 100644 apps/main/src/lib/components/ui/drawer/drawer-portal.svelte create mode 100644 apps/main/src/lib/components/ui/drawer/drawer-title.svelte create mode 100644 apps/main/src/lib/components/ui/drawer/drawer-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/drawer/drawer.svelte create mode 100644 apps/main/src/lib/components/ui/drawer/index.ts create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/index.ts create mode 100644 apps/main/src/lib/components/ui/empty/empty-content.svelte create mode 100644 apps/main/src/lib/components/ui/empty/empty-description.svelte create mode 100644 apps/main/src/lib/components/ui/empty/empty-header.svelte create mode 100644 apps/main/src/lib/components/ui/empty/empty-media.svelte create mode 100644 apps/main/src/lib/components/ui/empty/empty-title.svelte create mode 100644 apps/main/src/lib/components/ui/empty/empty.svelte create mode 100644 apps/main/src/lib/components/ui/empty/index.ts create mode 100644 apps/main/src/lib/components/ui/field/field-content.svelte create mode 100644 apps/main/src/lib/components/ui/field/field-description.svelte create mode 100644 apps/main/src/lib/components/ui/field/field-error.svelte create mode 100644 apps/main/src/lib/components/ui/field/field-group.svelte create mode 100644 apps/main/src/lib/components/ui/field/field-label.svelte create mode 100644 apps/main/src/lib/components/ui/field/field-legend.svelte create mode 100644 apps/main/src/lib/components/ui/field/field-separator.svelte create mode 100644 apps/main/src/lib/components/ui/field/field-set.svelte create mode 100644 apps/main/src/lib/components/ui/field/field-title.svelte create mode 100644 apps/main/src/lib/components/ui/field/field.svelte create mode 100644 apps/main/src/lib/components/ui/field/index.ts create mode 100644 apps/main/src/lib/components/ui/form/form-button.svelte create mode 100644 apps/main/src/lib/components/ui/form/form-description.svelte create mode 100644 apps/main/src/lib/components/ui/form/form-element-field.svelte create mode 100644 apps/main/src/lib/components/ui/form/form-field-errors.svelte create mode 100644 apps/main/src/lib/components/ui/form/form-field.svelte create mode 100644 apps/main/src/lib/components/ui/form/form-fieldset.svelte create mode 100644 apps/main/src/lib/components/ui/form/form-label.svelte create mode 100644 apps/main/src/lib/components/ui/form/form-legend.svelte create mode 100644 apps/main/src/lib/components/ui/form/index.ts create mode 100644 apps/main/src/lib/components/ui/hover-card/hover-card-content.svelte create mode 100644 apps/main/src/lib/components/ui/hover-card/hover-card-portal.svelte create mode 100644 apps/main/src/lib/components/ui/hover-card/hover-card-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/hover-card/hover-card.svelte create mode 100644 apps/main/src/lib/components/ui/hover-card/index.ts create mode 100644 apps/main/src/lib/components/ui/input-group/index.ts create mode 100644 apps/main/src/lib/components/ui/input-group/input-group-addon.svelte create mode 100644 apps/main/src/lib/components/ui/input-group/input-group-button.svelte create mode 100644 apps/main/src/lib/components/ui/input-group/input-group-input.svelte create mode 100644 apps/main/src/lib/components/ui/input-group/input-group-text.svelte create mode 100644 apps/main/src/lib/components/ui/input-group/input-group-textarea.svelte create mode 100644 apps/main/src/lib/components/ui/input-group/input-group.svelte create mode 100644 apps/main/src/lib/components/ui/input-otp/index.ts create mode 100644 apps/main/src/lib/components/ui/input-otp/input-otp-group.svelte create mode 100644 apps/main/src/lib/components/ui/input-otp/input-otp-separator.svelte create mode 100644 apps/main/src/lib/components/ui/input-otp/input-otp-slot.svelte create mode 100644 apps/main/src/lib/components/ui/input-otp/input-otp.svelte create mode 100644 apps/main/src/lib/components/ui/input/index.ts create mode 100644 apps/main/src/lib/components/ui/input/input.svelte create mode 100644 apps/main/src/lib/components/ui/item/index.ts create mode 100644 apps/main/src/lib/components/ui/item/item-actions.svelte create mode 100644 apps/main/src/lib/components/ui/item/item-content.svelte create mode 100644 apps/main/src/lib/components/ui/item/item-description.svelte create mode 100644 apps/main/src/lib/components/ui/item/item-footer.svelte create mode 100644 apps/main/src/lib/components/ui/item/item-group.svelte create mode 100644 apps/main/src/lib/components/ui/item/item-header.svelte create mode 100644 apps/main/src/lib/components/ui/item/item-media.svelte create mode 100644 apps/main/src/lib/components/ui/item/item-separator.svelte create mode 100644 apps/main/src/lib/components/ui/item/item-title.svelte create mode 100644 apps/main/src/lib/components/ui/item/item.svelte create mode 100644 apps/main/src/lib/components/ui/kbd/index.ts create mode 100644 apps/main/src/lib/components/ui/kbd/kbd-group.svelte create mode 100644 apps/main/src/lib/components/ui/kbd/kbd.svelte create mode 100644 apps/main/src/lib/components/ui/label/index.ts create mode 100644 apps/main/src/lib/components/ui/label/label.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/index.ts create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-checkbox-item.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-content.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-group-heading.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-group.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-item.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-label.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-menu.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-portal.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-radio-group.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-radio-item.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-separator.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-shortcut.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-sub-content.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-sub-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-sub.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar.svelte create mode 100644 apps/main/src/lib/components/ui/native-select/index.ts create mode 100644 apps/main/src/lib/components/ui/native-select/native-select-opt-group.svelte create mode 100644 apps/main/src/lib/components/ui/native-select/native-select-option.svelte create mode 100644 apps/main/src/lib/components/ui/native-select/native-select.svelte create mode 100644 apps/main/src/lib/components/ui/navigation-menu/index.ts create mode 100644 apps/main/src/lib/components/ui/navigation-menu/navigation-menu-content.svelte create mode 100644 apps/main/src/lib/components/ui/navigation-menu/navigation-menu-indicator.svelte create mode 100644 apps/main/src/lib/components/ui/navigation-menu/navigation-menu-item.svelte create mode 100644 apps/main/src/lib/components/ui/navigation-menu/navigation-menu-link.svelte create mode 100644 apps/main/src/lib/components/ui/navigation-menu/navigation-menu-list.svelte create mode 100644 apps/main/src/lib/components/ui/navigation-menu/navigation-menu-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/navigation-menu/navigation-menu-viewport.svelte create mode 100644 apps/main/src/lib/components/ui/navigation-menu/navigation-menu.svelte create mode 100644 apps/main/src/lib/components/ui/pagination/index.ts create mode 100644 apps/main/src/lib/components/ui/pagination/pagination-content.svelte create mode 100644 apps/main/src/lib/components/ui/pagination/pagination-ellipsis.svelte create mode 100644 apps/main/src/lib/components/ui/pagination/pagination-item.svelte create mode 100644 apps/main/src/lib/components/ui/pagination/pagination-link.svelte create mode 100644 apps/main/src/lib/components/ui/pagination/pagination-next-button.svelte create mode 100644 apps/main/src/lib/components/ui/pagination/pagination-next.svelte create mode 100644 apps/main/src/lib/components/ui/pagination/pagination-prev-button.svelte create mode 100644 apps/main/src/lib/components/ui/pagination/pagination-previous.svelte create mode 100644 apps/main/src/lib/components/ui/pagination/pagination.svelte create mode 100644 apps/main/src/lib/components/ui/popover/index.ts create mode 100644 apps/main/src/lib/components/ui/popover/popover-close.svelte create mode 100644 apps/main/src/lib/components/ui/popover/popover-content.svelte create mode 100644 apps/main/src/lib/components/ui/popover/popover-portal.svelte create mode 100644 apps/main/src/lib/components/ui/popover/popover-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/popover/popover.svelte create mode 100644 apps/main/src/lib/components/ui/progress/index.ts create mode 100644 apps/main/src/lib/components/ui/progress/progress.svelte create mode 100644 apps/main/src/lib/components/ui/radio-group/index.ts create mode 100644 apps/main/src/lib/components/ui/radio-group/radio-group-item.svelte create mode 100644 apps/main/src/lib/components/ui/radio-group/radio-group.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/index.ts create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-caption.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-cell.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-day.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-grid-body.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-grid-head.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-grid-row.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-grid.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-head-cell.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-header.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-heading.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-month-select.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-month.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-months.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-nav.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-next-button.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-prev-button.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-year-select.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar.svelte create mode 100644 apps/main/src/lib/components/ui/resizable/index.ts create mode 100644 apps/main/src/lib/components/ui/resizable/resizable-handle.svelte create mode 100644 apps/main/src/lib/components/ui/resizable/resizable-pane-group.svelte create mode 100644 apps/main/src/lib/components/ui/scroll-area/index.ts create mode 100644 apps/main/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte create mode 100644 apps/main/src/lib/components/ui/scroll-area/scroll-area.svelte create mode 100644 apps/main/src/lib/components/ui/select/index.ts create mode 100644 apps/main/src/lib/components/ui/select/select-content.svelte create mode 100644 apps/main/src/lib/components/ui/select/select-group-heading.svelte create mode 100644 apps/main/src/lib/components/ui/select/select-group.svelte create mode 100644 apps/main/src/lib/components/ui/select/select-item.svelte create mode 100644 apps/main/src/lib/components/ui/select/select-label.svelte create mode 100644 apps/main/src/lib/components/ui/select/select-portal.svelte create mode 100644 apps/main/src/lib/components/ui/select/select-scroll-down-button.svelte create mode 100644 apps/main/src/lib/components/ui/select/select-scroll-up-button.svelte create mode 100644 apps/main/src/lib/components/ui/select/select-separator.svelte create mode 100644 apps/main/src/lib/components/ui/select/select-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/select/select.svelte create mode 100644 apps/main/src/lib/components/ui/separator/index.ts create mode 100644 apps/main/src/lib/components/ui/separator/separator.svelte create mode 100644 apps/main/src/lib/components/ui/sheet/index.ts create mode 100644 apps/main/src/lib/components/ui/sheet/sheet-close.svelte create mode 100644 apps/main/src/lib/components/ui/sheet/sheet-content.svelte create mode 100644 apps/main/src/lib/components/ui/sheet/sheet-description.svelte create mode 100644 apps/main/src/lib/components/ui/sheet/sheet-footer.svelte create mode 100644 apps/main/src/lib/components/ui/sheet/sheet-header.svelte create mode 100644 apps/main/src/lib/components/ui/sheet/sheet-overlay.svelte create mode 100644 apps/main/src/lib/components/ui/sheet/sheet-portal.svelte create mode 100644 apps/main/src/lib/components/ui/sheet/sheet-title.svelte create mode 100644 apps/main/src/lib/components/ui/sheet/sheet-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/sheet/sheet.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/constants.ts create mode 100644 apps/main/src/lib/components/ui/sidebar/context.svelte.ts create mode 100644 apps/main/src/lib/components/ui/sidebar/index.ts create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-content.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-footer.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-group-action.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-group-content.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-group-label.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-group.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-header.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-input.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-inset.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-menu-action.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-menu-button.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-menu-item.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-menu.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-provider.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-rail.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-separator.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar.svelte create mode 100644 apps/main/src/lib/components/ui/skeleton/index.ts create mode 100644 apps/main/src/lib/components/ui/skeleton/skeleton.svelte create mode 100644 apps/main/src/lib/components/ui/slider/index.ts create mode 100644 apps/main/src/lib/components/ui/slider/slider.svelte create mode 100644 apps/main/src/lib/components/ui/sonner/index.ts create mode 100644 apps/main/src/lib/components/ui/sonner/sonner.svelte create mode 100644 apps/main/src/lib/components/ui/spinner/index.ts create mode 100644 apps/main/src/lib/components/ui/spinner/spinner.svelte create mode 100644 apps/main/src/lib/components/ui/switch/index.ts create mode 100644 apps/main/src/lib/components/ui/switch/switch.svelte create mode 100644 apps/main/src/lib/components/ui/table/index.ts create mode 100644 apps/main/src/lib/components/ui/table/table-body.svelte create mode 100644 apps/main/src/lib/components/ui/table/table-caption.svelte create mode 100644 apps/main/src/lib/components/ui/table/table-cell.svelte create mode 100644 apps/main/src/lib/components/ui/table/table-footer.svelte create mode 100644 apps/main/src/lib/components/ui/table/table-head.svelte create mode 100644 apps/main/src/lib/components/ui/table/table-header.svelte create mode 100644 apps/main/src/lib/components/ui/table/table-row.svelte create mode 100644 apps/main/src/lib/components/ui/table/table.svelte create mode 100644 apps/main/src/lib/components/ui/tabs/index.ts create mode 100644 apps/main/src/lib/components/ui/tabs/tabs-content.svelte create mode 100644 apps/main/src/lib/components/ui/tabs/tabs-list.svelte create mode 100644 apps/main/src/lib/components/ui/tabs/tabs-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/tabs/tabs.svelte create mode 100644 apps/main/src/lib/components/ui/textarea/index.ts create mode 100644 apps/main/src/lib/components/ui/textarea/textarea.svelte create mode 100644 apps/main/src/lib/components/ui/toggle-group/index.ts create mode 100644 apps/main/src/lib/components/ui/toggle-group/toggle-group-item.svelte create mode 100644 apps/main/src/lib/components/ui/toggle-group/toggle-group.svelte create mode 100644 apps/main/src/lib/components/ui/toggle/index.ts create mode 100644 apps/main/src/lib/components/ui/toggle/toggle.svelte create mode 100644 apps/main/src/lib/components/ui/tooltip/index.ts create mode 100644 apps/main/src/lib/components/ui/tooltip/tooltip-content.svelte create mode 100644 apps/main/src/lib/components/ui/tooltip/tooltip-portal.svelte create mode 100644 apps/main/src/lib/components/ui/tooltip/tooltip-provider.svelte create mode 100644 apps/main/src/lib/components/ui/tooltip/tooltip-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/tooltip/tooltip.svelte create mode 100644 apps/main/src/lib/core/constants.ts create mode 100644 apps/main/src/lib/currency.utils.ts create mode 100644 apps/main/src/lib/domains/account/account.vm.svelte.ts create mode 100644 apps/main/src/lib/domains/account/sessions/sessions-card.svelte create mode 100644 apps/main/src/lib/domains/account/sessions/sessions.vm.svelte.ts create mode 100644 apps/main/src/lib/domains/notifications/notification.vm.svelte.ts create mode 100644 apps/main/src/lib/domains/notifications/notifications-table.svelte create mode 100644 apps/main/src/lib/domains/security/auth.vm.svelte.ts create mode 100644 apps/main/src/lib/domains/security/email-login-form.svelte create mode 100644 apps/main/src/lib/domains/security/magic-link-verification.svelte create mode 100644 apps/main/src/lib/domains/todo/list.svelte create mode 100644 apps/main/src/lib/global.stores.ts create mode 100644 apps/main/src/lib/hooks/is-mobile.svelte.ts create mode 100644 apps/main/src/lib/make-client.ts create mode 100644 apps/main/src/lib/utils.ts create mode 100644 apps/main/src/routes/(main)/+layout.server.ts create mode 100644 apps/main/src/routes/(main)/+layout.svelte create mode 100644 apps/main/src/routes/(main)/+page.server.ts create mode 100644 apps/main/src/routes/(main)/+page.svelte create mode 100644 apps/main/src/routes/(main)/account/+layout.svelte create mode 100644 apps/main/src/routes/(main)/account/+page.svelte create mode 100644 apps/main/src/routes/(main)/account/verify-email/+page.svelte create mode 100644 apps/main/src/routes/(main)/dashboard/+page.svelte create mode 100644 apps/main/src/routes/(main)/notifications/+page.svelte create mode 100644 apps/main/src/routes/(main)/users/+page.server.ts create mode 100644 apps/main/src/routes/(main)/users/+page.svelte create mode 100644 apps/main/src/routes/+layout.svelte create mode 100644 apps/main/src/routes/api/debug/users/+server.ts create mode 100644 apps/main/src/routes/api/v1/[...paths]/+server.ts create mode 100644 apps/main/src/routes/auth/2fa/+layout.server.ts create mode 100644 apps/main/src/routes/auth/2fa/+page.svelte create mode 100644 apps/main/src/routes/auth/login/+page.server.ts create mode 100644 apps/main/src/routes/auth/login/+page.svelte create mode 100644 apps/main/src/routes/auth/magic-link/+page.svelte create mode 100644 apps/main/src/routes/layout.css create mode 100644 apps/main/static/favicon.png create mode 100644 apps/main/static/fonts/manrope-variable.ttf create mode 100644 apps/main/static/images/avatar.png create mode 100644 apps/main/static/robots.txt create mode 100644 apps/main/svelte.config.js create mode 100644 apps/main/tsconfig.json create mode 100644 apps/main/vite.config.ts create mode 100644 apps/processor/.gitignore create mode 100644 apps/processor/README.md create mode 100644 apps/processor/package.json create mode 100644 apps/processor/src/index.ts create mode 100644 apps/processor/tsconfig.json create mode 100644 bun.lock create mode 100644 dev/README.md create mode 100644 dev/clickhouse-cluster.xml create mode 100644 dev/clickhouse-config.xml create mode 100644 dev/clickhouse-custom-function.xml create mode 100644 dev/clickhouse-users.xml create mode 100644 dev/docker-compose.dev.yaml create mode 100644 dev/otel-collector-config.yaml create mode 100644 dev/otel-collector-opamp-config.yaml create mode 100644 dockerfiles/main.Dockerfile create mode 100644 dockerfiles/migrator.Dockerfile create mode 100644 dockerfiles/processor.Dockerfile create mode 100644 package.json create mode 100644 packages/db/drizzle.config.ts create mode 100644 packages/db/index.ts create mode 100644 packages/db/migrations/0000_woozy_mother_askani.sql create mode 100644 packages/db/migrations/meta/0000_snapshot.json create mode 100644 packages/db/migrations/meta/_journal.json create mode 100644 packages/db/package.json create mode 100644 packages/db/schema/auth.schema.ts create mode 100644 packages/db/schema/better.auth.schema.ts create mode 100644 packages/db/schema/general.schema.ts create mode 100644 packages/db/schema/index.ts create mode 100644 packages/logger/client.ts create mode 100644 packages/logger/index.ts create mode 100644 packages/logger/package.json create mode 100644 packages/logger/tsconfig.json create mode 100644 packages/logic/core/array.utils.ts create mode 100644 packages/logic/core/data/countries.ts create mode 100644 packages/logic/core/data/phonecc.ts create mode 100644 packages/logic/core/date.utils.ts create mode 100644 packages/logic/core/error.ts create mode 100644 packages/logic/core/flow.execution.context.ts create mode 100644 packages/logic/core/hash.utils.ts create mode 100644 packages/logic/core/hono.helpers.ts create mode 100644 packages/logic/core/pagination.utils.ts create mode 100644 packages/logic/core/rate.limiter.ts create mode 100644 packages/logic/core/settings.ts create mode 100644 packages/logic/core/string.utils/index.ts create mode 100644 packages/logic/core/string.utils/sequence.matcher.ts create mode 100644 packages/logic/domains/2fa/controller.ts create mode 100644 packages/logic/domains/2fa/data.ts create mode 100644 packages/logic/domains/2fa/errors.ts create mode 100644 packages/logic/domains/2fa/repository.ts create mode 100644 packages/logic/domains/2fa/router.ts create mode 100644 packages/logic/domains/2fa/sensitive-actions.ts create mode 100644 packages/logic/domains/auth/config.base.ts create mode 100644 packages/logic/domains/auth/controller.ts create mode 100644 packages/logic/domains/auth/errors.ts create mode 100644 packages/logic/domains/notifications/controller.ts create mode 100644 packages/logic/domains/notifications/data.ts create mode 100644 packages/logic/domains/notifications/errors.ts create mode 100644 packages/logic/domains/notifications/repository.ts create mode 100644 packages/logic/domains/notifications/router.ts create mode 100644 packages/logic/domains/user/account.repository.ts create mode 100644 packages/logic/domains/user/controller.ts create mode 100644 packages/logic/domains/user/data.ts create mode 100644 packages/logic/domains/user/errors.ts create mode 100644 packages/logic/domains/user/repository.ts create mode 100644 packages/logic/domains/user/router.ts create mode 100644 packages/logic/package.json create mode 100644 packages/logic/tsconfig.json create mode 100644 packages/redis/index.ts create mode 100644 packages/redis/package.json create mode 100644 packages/result/index.ts create mode 100644 packages/result/package.json create mode 100644 packages/settings/index.ts create mode 100644 packages/settings/package.json create mode 100755 scripts/migrate.sh create mode 100755 scripts/populate.env.sh create mode 100755 scripts/prod.start.sh create mode 100644 tsconfig.json create mode 100644 turbo.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2e2ad5c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,61 @@ +.zed + +# Dependencies +node_modules +.pnp +.pnp.js + +__pycache__ +.venv + +# ignore generated log files +**/logs/**.log +**/logs/**.log.gz +**/logs/**-audit.json + +**/data/credentials/** +**/testdocs/** + +ot_res.json +out.json +payload.json + +screenshots/*.jpeg +screenshots/*.png +screenshots/*.jpg + +# Local env files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Testing +coverage + +# Turbo +.turbo + +# Vercel +.vercel + +# Build Outputs +.next/ +out/ +build +dist +.svelte-kit + +# Debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Misc +.DS_Store +*.pem + +creds.md + +onlydevs diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..987f136 --- /dev/null +++ b/.env.example @@ -0,0 +1,49 @@ +APP_NAME=${{project.APP_NAME}} +NODE_ENV=${{project.NODE_ENV}} + +REDIS_URL=${{project.REDIS_URL}} +DATABASE_URL=${{project.DATABASE_URL}} + +COUCHBASE_CONNECTION_STRING=${{project.COUCHBASE_CONNECTION_STRING}} +COUCHBASE_USERNAME=${{project.COUCHBASE_USERNAME}} +COUCHBASE_PASSWORD=${{project.COUCHBASE_PASSWORD}} + +QDRANT_URL=${{project.QDRANT_URL}} +QDRANT_API_KEY=${{project.QDRANT_API_KEY}} + +BETTER_AUTH_SECRET=${{project.BETTER_AUTH_SECRET}} +BETTER_AUTH_URL=${{project.BETTER_AUTH_URL}} + +GOOGLE_CLIENT_ID=${{project.GOOGLE_CLIENT_ID}} +GOOGLE_CLIENT_SECRET=${{project.GOOGLE_CLIENT_SECRET}} +GOOGLE_OAUTH_SERVER_URL=${{project.GOOGLE_OAUTH_SERVER_URL}} +GOOGLE_OAUTH_SERVER_PORT=${{project.GOOGLE_OAUTH_SERVER_PORT}} + +GEMINI_API_KEY=${{project.GEMINI_API_KEY}} + +RESEND_API_KEY=${{project.RESEND_API_KEY}} +FROM_EMAIL=${{project.FROM_EMAIL}} + +INTERNAL_API_KEY="supersecretkey" + +PROCESSOR_API_URL=${{project.PROCESSOR_API_URL}} + +OPENROUTER_API_KEY=${{project.OPENROUTER_API_KEY}} +MODEL_NAME=${{project.MODEL_NAME}} + +OCR_SERVICE_URL=${{project.OCR_SERVICE_URL}} +OCR_SERVICE_TIMEOUT=${{project.OCR_SERVICE_TIMEOUT}} +OCR_SERVICE_MAX_RETRIES=${{project.OCR_SERVICE_MAX_RETRIES}} +OCR_FORCE_OCR=${{project.OCR_FORCE_OCR}} +OCR_DEBUG=${{project.OCR_DEBUG}} + +R2_BUCKET_NAME=${{project.R2_BUCKET_NAME}} +R2_REGION=${{project.R2_REGION}} +R2_ENDPOINT=${{project.R2_ENDPOINT}} +R2_ACCESS_KEY=${{project.R2_ACCESS_KEY}} +R2_SECRET_KEY=${{project.R2_SECRET_KEY}} +R2_PUBLIC_URL=${{project.R2_PUBLIC_URL}} + +MAX_FILE_SIZE=${{project.MAX_FILE_SIZE}} +ALLOWED_MIME_TYPES=${{project.ALLOWED_MIME_TYPES}} +ALLOWED_EXTENSIONS=${{project.ALLOWED_EXTENSIONS}} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e9e4835 --- /dev/null +++ b/.gitignore @@ -0,0 +1,63 @@ +.zed + +# Dependencies +node_modules +.pnp +.pnp.js + +__pycache__ +.venv + +# ignore generated log files +**/logs/**.log +**/logs/**.log.gz +**/logs/**-audit.json + +**/data/credentials/** +**/testdocs/** + +scripts/whatsapp.req.sh + +ot_res.json +out.json +payload.json + +screenshots/*.jpeg +screenshots/*.png +screenshots/*.jpg + +# Local env files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Testing +coverage + +# Turbo +.turbo + +# Vercel +.vercel + +# Build Outputs +.next/ +out/ +build +dist +.svelte-kit + +# Debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Misc +.DS_Store +*.pem + +creds.md + +onlydevs diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md new file mode 100644 index 0000000..6c10e55 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# bare web application monorepo + +for practice and showcase purpose + +--- diff --git a/apps/main/.gitignore b/apps/main/.gitignore new file mode 100644 index 0000000..3b462cb --- /dev/null +++ b/apps/main/.gitignore @@ -0,0 +1,23 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/apps/main/.npmrc b/apps/main/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/apps/main/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/apps/main/.prettierignore b/apps/main/.prettierignore new file mode 100644 index 0000000..7d74fe2 --- /dev/null +++ b/apps/main/.prettierignore @@ -0,0 +1,9 @@ +# Package Managers +package-lock.json +pnpm-lock.yaml +yarn.lock +bun.lock +bun.lockb + +# Miscellaneous +/static/ diff --git a/apps/main/README.md b/apps/main/README.md new file mode 100644 index 0000000..75842c4 --- /dev/null +++ b/apps/main/README.md @@ -0,0 +1,38 @@ +# sv + +Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```sh +# create a new project in the current directory +npx sv create + +# create a new project in my-app +npx sv create my-app +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```sh +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +To create a production version of your app: + +```sh +npm run build +``` + +You can preview the production build with `npm run preview`. + +> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. diff --git a/apps/main/components.json b/apps/main/components.json new file mode 100644 index 0000000..1e6a638 --- /dev/null +++ b/apps/main/components.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://shadcn-svelte.com/schema.json", + "tailwind": { + "css": "src/routes/layout.css", + "baseColor": "neutral" + }, + "aliases": { + "components": "$lib/components", + "utils": "$lib/utils", + "ui": "$lib/components/ui", + "hooks": "$lib/hooks", + "lib": "$lib" + }, + "typescript": true, + "registry": "https://shadcn-svelte.com/registry" +} diff --git a/apps/main/package.json b/apps/main/package.json new file mode 100644 index 0000000..aa9f103 --- /dev/null +++ b/apps/main/package.json @@ -0,0 +1,71 @@ +{ + "name": "@apps/main", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev --port 5173", + "build": "NODE_ENV=build vite build", + "prod": "HOST=0.0.0.0 PORT=3000 bun ./build/index.js", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "format": "prettier --write .", + "lint": "prettier --check .", + "test:unit": "vitest", + "test": "npm run test:unit -- --run" + }, + "dependencies": { + "@pkg/db": "workspace:*", + "@pkg/logger": "workspace:*", + "@pkg/logic": "workspace:*", + "@pkg/result": "workspace:*", + "@tanstack/svelte-query": "^6.0.10", + "better-auth": "^1.4.7", + "date-fns": "^4.1.0", + "hono": "^4.11.1", + "marked": "^17.0.1", + "nanoid": "^5.1.6", + "neverthrow": "^8.2.0", + "qrcode": "^1.5.4", + "valibot": "^1.2.0" + }, + "devDependencies": { + "@iconify/json": "^2.2.434", + "@internationalized/date": "^3.10.0", + "@lucide/svelte": "^0.561.0", + "@sveltejs/adapter-auto": "^7.0.0", + "@sveltejs/kit": "^2.49.1", + "@sveltejs/vite-plugin-svelte": "^6.2.1", + "@tailwindcss/forms": "^0.5.10", + "@tailwindcss/typography": "^0.5.19", + "@tailwindcss/vite": "^4.1.18", + "@tanstack/table-core": "^8.21.3", + "@types/qrcode": "^1.5.6", + "bits-ui": "^2.14.4", + "clsx": "^2.1.1", + "embla-carousel-svelte": "^8.6.0", + "formsnap": "^2.0.1", + "layerchart": "2.0.0-next.43", + "mode-watcher": "^1.1.0", + "paneforge": "^1.0.2", + "prettier": "^3.7.4", + "prettier-plugin-svelte": "^3.4.0", + "prettier-plugin-tailwindcss": "^0.7.2", + "svelte": "^5.45.6", + "svelte-adapter-bun": "^1.0.1", + "svelte-check": "^4.3.4", + "svelte-sonner": "^1.0.7", + "sveltekit-superforms": "^2.28.1", + "tailwind-merge": "^3.4.0", + "tailwind-variants": "^3.2.2", + "tailwindcss": "^4.1.18", + "tw-animate-css": "^1.4.0", + "typescript": "^5.9.3", + "unplugin-icons": "^23.0.1", + "vaul-svelte": "^1.0.0-next.7", + "vite": "^7.2.6", + "vitest": "^4.0.15" + } +} diff --git a/apps/main/src/app.d.ts b/apps/main/src/app.d.ts new file mode 100644 index 0000000..7ded0fe --- /dev/null +++ b/apps/main/src/app.d.ts @@ -0,0 +1,20 @@ +import type { Session, User } from "@pkg/logic/domains/user/data"; +import "unplugin-icons/types/svelte"; + +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + interface Locals { + flowId?: string; + session?: Session; + user?: User; + } + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/apps/main/src/app.html b/apps/main/src/app.html new file mode 100644 index 0000000..f273cc5 --- /dev/null +++ b/apps/main/src/app.html @@ -0,0 +1,11 @@ + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/apps/main/src/demo.spec.ts b/apps/main/src/demo.spec.ts new file mode 100644 index 0000000..e07cbbd --- /dev/null +++ b/apps/main/src/demo.spec.ts @@ -0,0 +1,7 @@ +import { describe, it, expect } from 'vitest'; + +describe('sum test', () => { + it('adds 1 + 2 to equal 3', () => { + expect(1 + 2).toBe(3); + }); +}); diff --git a/apps/main/src/hooks.server.ts b/apps/main/src/hooks.server.ts new file mode 100644 index 0000000..705368f --- /dev/null +++ b/apps/main/src/hooks.server.ts @@ -0,0 +1,79 @@ +import { checkInitial2FaRequired } from "@pkg/logic/domains/2fa/sensitive-actions"; +import type { Handle, HandleServerError } from "@sveltejs/kit"; +import { auth } from "@pkg/logic/domains/auth/config.base"; +import { svelteKitHandler } from "better-auth/svelte-kit"; +import type { User } from "@pkg/logic/domains/user/data"; +import { sequence } from "@sveltejs/kit/hooks"; +import { building } from "$app/environment"; + +export const handleError: HandleServerError = async ({ error, event }) => { + console.log("[-] Running error middleware for : ", event.url.pathname); + console.log(error); + return { message: (error as Error).message ?? "Internal Server Error" }; +}; + +export const zero: Handle = async ({ event, resolve }) => { + return svelteKitHandler({ event, resolve, auth, building }); +}; + +export const first: Handle = async ({ event, resolve }) => { + if ( + event.url.pathname.includes("/api/v1") || + event.url.pathname.includes("/api/auth") || + event.url.pathname.includes("/api/debug") || + event.url.pathname.includes("/api/chat") || + event.url.pathname.includes("/legal") || + event.url.pathname.includes("/auth") + ) { + return await resolve(event); + } + console.log("[+] Running middleware for : ", event.url.pathname); + const baseUrl = event.url.origin; + const signInUrl = baseUrl + "/auth/login"; + const isSignInPage = event.url.pathname === "/auth/login"; + const redirectResponse = new Response(null, { + status: 302, + headers: { Location: signInUrl }, + }); + + const u = await auth.api.getSession({ headers: event.request.headers }); + if (!u || !u.user || !u.session) { + return redirectResponse; + } + if (u.user && isSignInPage) { + return new Response(null, { + status: 302, + headers: { Location: baseUrl }, + }); + } + + console.log("Setting user & session to locals"); + + const fid = crypto.randomUUID(); + + event.locals.flowId = fid; + event.locals.session = u.session; + event.locals.user = u.user as any as User; + + const needs2FA = await checkInitial2FaRequired( + { + flowId: fid, + userId: u.user.id, + sessionId: u.session.id, + }, + u.user as any, + u.session.id, + ); + if (needs2FA && !event.url.pathname.includes("/auth/2fa")) { + return new Response(null, { + status: 302, + headers: { + Location: `/auth/2fa?redirect=${encodeURIComponent(event.url.pathname)}`, + }, + }); + } + + return resolve(event); +}; + +export const handle = sequence(zero, first); diff --git a/apps/main/src/lib/api.ts b/apps/main/src/lib/api.ts new file mode 100644 index 0000000..4ec94cd --- /dev/null +++ b/apps/main/src/lib/api.ts @@ -0,0 +1,15 @@ +import { notificationsRouter } from "@pkg/logic/domains/notifications/router"; +import { usersRouter } from "@pkg/logic/domains/user/router"; +import { twofaRouter } from "@pkg/logic/domains/2fa/router"; +import { Hono } from "hono"; + +const baseRouter = new Hono() + .route("/users", usersRouter) + .route("/twofactor", twofaRouter) + .route("/notifications", notificationsRouter); + +export const apiBasePath = "/api/v1"; + +export const api = new Hono().route(apiBasePath, baseRouter); + +export type Router = typeof baseRouter; diff --git a/apps/main/src/lib/auth.client.ts b/apps/main/src/lib/auth.client.ts new file mode 100644 index 0000000..ddfc649 --- /dev/null +++ b/apps/main/src/lib/auth.client.ts @@ -0,0 +1,21 @@ +import { + adminClient, + customSessionClient, + inferAdditionalFields, + magicLinkClient, + multiSessionClient, + usernameClient, +} from "better-auth/client/plugins"; +import type { auth } from "@pkg/logic/domains/auth/config.base"; +import { createAuthClient } from "better-auth/svelte"; + +export const authClient = createAuthClient({ + plugins: [ + usernameClient(), + adminClient(), + magicLinkClient(), + multiSessionClient(), + customSessionClient(), + inferAdditionalFields(), + ], +}); diff --git a/apps/main/src/lib/components/app-sidebar.svelte b/apps/main/src/lib/components/app-sidebar.svelte new file mode 100644 index 0000000..f0ed540 --- /dev/null +++ b/apps/main/src/lib/components/app-sidebar.svelte @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + diff --git a/apps/main/src/lib/components/atoms/button-text.svelte b/apps/main/src/lib/components/atoms/button-text.svelte new file mode 100644 index 0000000..207d697 --- /dev/null +++ b/apps/main/src/lib/components/atoms/button-text.svelte @@ -0,0 +1,17 @@ + + +{#if loading} + + {loadingText} +{:else} + {text} +{/if} diff --git a/apps/main/src/lib/components/atoms/icon.svelte b/apps/main/src/lib/components/atoms/icon.svelte new file mode 100644 index 0000000..794c944 --- /dev/null +++ b/apps/main/src/lib/components/atoms/icon.svelte @@ -0,0 +1,11 @@ + + + diff --git a/apps/main/src/lib/components/atoms/title.svelte b/apps/main/src/lib/components/atoms/title.svelte new file mode 100644 index 0000000..ed17511 --- /dev/null +++ b/apps/main/src/lib/components/atoms/title.svelte @@ -0,0 +1,61 @@ + + + + {@render children?.()} + diff --git a/apps/main/src/lib/components/molecules/google-oauth-button.svelte b/apps/main/src/lib/components/molecules/google-oauth-button.svelte new file mode 100644 index 0000000..18aa308 --- /dev/null +++ b/apps/main/src/lib/components/molecules/google-oauth-button.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/molecules/markdown-renderer.svelte b/apps/main/src/lib/components/molecules/markdown-renderer.svelte new file mode 100644 index 0000000..6459612 --- /dev/null +++ b/apps/main/src/lib/components/molecules/markdown-renderer.svelte @@ -0,0 +1,183 @@ + + + + + + + + +
+ {@html marked(content)} +
+ + diff --git a/apps/main/src/lib/components/molecules/max-width-wrapper.svelte b/apps/main/src/lib/components/molecules/max-width-wrapper.svelte new file mode 100644 index 0000000..e3ec428 --- /dev/null +++ b/apps/main/src/lib/components/molecules/max-width-wrapper.svelte @@ -0,0 +1,11 @@ + + +
+ {@render children()} +
diff --git a/apps/main/src/lib/components/nav-main.svelte b/apps/main/src/lib/components/nav-main.svelte new file mode 100644 index 0000000..5968981 --- /dev/null +++ b/apps/main/src/lib/components/nav-main.svelte @@ -0,0 +1,81 @@ + + + + + {#each items as mainItem (mainItem.title)} + + + +
+ + + {#snippet child({ props })} + + {#if mainItem.icon} + + {/if} + {mainItem.title} + + {/snippet} + + + + {#if mainItem.items && sidebar.open} + + {#snippet child({ props })} +
+ + Toggle {mainItem.title} submenu +
+ {/snippet} +
+ {/if} +
+ + + + {#if mainItem.items} + + {#each mainItem.items as subItem (subItem.title)} + + + {#snippet child({ props })} + + {subItem.title} + + {/snippet} + + + {/each} + + {/if} + +
+
+ {/each} +
+
diff --git a/apps/main/src/lib/components/nav-user.svelte b/apps/main/src/lib/components/nav-user.svelte new file mode 100644 index 0000000..0a9e96a --- /dev/null +++ b/apps/main/src/lib/components/nav-user.svelte @@ -0,0 +1,150 @@ + + + + + + + {#snippet child({ props })} + + + + + {user.name.slice(0, 2).toUpperCase()} + + +
+ {user.name} + {user.email} +
+ +
+ {/snippet} +
+ + +
+ + + + {user.name.slice(0, 2).toUpperCase()} + + +
+ {user.name} + {user.email} +
+
+
+ + + + {#each secondaryNavTree as item (item.title)} + + + + {item.title} + + + {/each} + + + + + + + + + + Theme + + + setMode("light")}> + + Light + + setMode("dark")}> + + Dark + + resetMode()}> + + System + + + + + + logoutUser()}> + + Log out + +
+
+
+
diff --git a/apps/main/src/lib/components/team-switcher.svelte b/apps/main/src/lib/components/team-switcher.svelte new file mode 100644 index 0000000..4981212 --- /dev/null +++ b/apps/main/src/lib/components/team-switcher.svelte @@ -0,0 +1,80 @@ + + + + + + + {#snippet child({ props })} + +
+ +
+
+ + {activeTeam.name} + + {activeTeam.plan} +
+ +
+ {/snippet} +
+ + Teams + {#each teams as team, index (team.name)} + (activeTeam = team)} + class="gap-2 p-2" + > +
+ +
+ {team.name} + > +
+ {/each} + + +
+ +
+
+ Coming soon +
+
+
+
+
+
diff --git a/apps/main/src/lib/components/ui/accordion/accordion-content.svelte b/apps/main/src/lib/components/ui/accordion/accordion-content.svelte new file mode 100644 index 0000000..559db3d --- /dev/null +++ b/apps/main/src/lib/components/ui/accordion/accordion-content.svelte @@ -0,0 +1,22 @@ + + + +
+ {@render children?.()} +
+
diff --git a/apps/main/src/lib/components/ui/accordion/accordion-item.svelte b/apps/main/src/lib/components/ui/accordion/accordion-item.svelte new file mode 100644 index 0000000..780545c --- /dev/null +++ b/apps/main/src/lib/components/ui/accordion/accordion-item.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/accordion/accordion-trigger.svelte b/apps/main/src/lib/components/ui/accordion/accordion-trigger.svelte new file mode 100644 index 0000000..c46c246 --- /dev/null +++ b/apps/main/src/lib/components/ui/accordion/accordion-trigger.svelte @@ -0,0 +1,32 @@ + + + + svg]:rotate-180", + className + )} + {...restProps} + > + {@render children?.()} + + + diff --git a/apps/main/src/lib/components/ui/accordion/accordion.svelte b/apps/main/src/lib/components/ui/accordion/accordion.svelte new file mode 100644 index 0000000..117ee37 --- /dev/null +++ b/apps/main/src/lib/components/ui/accordion/accordion.svelte @@ -0,0 +1,16 @@ + + + diff --git a/apps/main/src/lib/components/ui/accordion/index.ts b/apps/main/src/lib/components/ui/accordion/index.ts new file mode 100644 index 0000000..ac343a1 --- /dev/null +++ b/apps/main/src/lib/components/ui/accordion/index.ts @@ -0,0 +1,16 @@ +import Root from "./accordion.svelte"; +import Content from "./accordion-content.svelte"; +import Item from "./accordion-item.svelte"; +import Trigger from "./accordion-trigger.svelte"; + +export { + Root, + Content, + Item, + Trigger, + // + Root as Accordion, + Content as AccordionContent, + Item as AccordionItem, + Trigger as AccordionTrigger, +}; diff --git a/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte new file mode 100644 index 0000000..a005691 --- /dev/null +++ b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte @@ -0,0 +1,18 @@ + + + diff --git a/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte new file mode 100644 index 0000000..a7b0cf7 --- /dev/null +++ b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte @@ -0,0 +1,18 @@ + + + diff --git a/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte new file mode 100644 index 0000000..00bdd9c --- /dev/null +++ b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte @@ -0,0 +1,29 @@ + + + + + + diff --git a/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte new file mode 100644 index 0000000..2ec67dc --- /dev/null +++ b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte new file mode 100644 index 0000000..f78b97a --- /dev/null +++ b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte new file mode 100644 index 0000000..1835d91 --- /dev/null +++ b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte new file mode 100644 index 0000000..a64ee76 --- /dev/null +++ b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte @@ -0,0 +1,20 @@ + + + diff --git a/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte new file mode 100644 index 0000000..f0a19a8 --- /dev/null +++ b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte new file mode 100644 index 0000000..7ef2b5f --- /dev/null +++ b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte new file mode 100644 index 0000000..b22d1d5 --- /dev/null +++ b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/alert-dialog/alert-dialog.svelte b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog.svelte new file mode 100644 index 0000000..7ea78bb --- /dev/null +++ b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/alert-dialog/index.ts b/apps/main/src/lib/components/ui/alert-dialog/index.ts new file mode 100644 index 0000000..269538e --- /dev/null +++ b/apps/main/src/lib/components/ui/alert-dialog/index.ts @@ -0,0 +1,37 @@ +import Root from "./alert-dialog.svelte"; +import Portal from "./alert-dialog-portal.svelte"; +import Trigger from "./alert-dialog-trigger.svelte"; +import Title from "./alert-dialog-title.svelte"; +import Action from "./alert-dialog-action.svelte"; +import Cancel from "./alert-dialog-cancel.svelte"; +import Footer from "./alert-dialog-footer.svelte"; +import Header from "./alert-dialog-header.svelte"; +import Overlay from "./alert-dialog-overlay.svelte"; +import Content from "./alert-dialog-content.svelte"; +import Description from "./alert-dialog-description.svelte"; + +export { + Root, + Title, + Action, + Cancel, + Portal, + Footer, + Header, + Trigger, + Overlay, + Content, + Description, + // + Root as AlertDialog, + Title as AlertDialogTitle, + Action as AlertDialogAction, + Cancel as AlertDialogCancel, + Portal as AlertDialogPortal, + Footer as AlertDialogFooter, + Header as AlertDialogHeader, + Trigger as AlertDialogTrigger, + Overlay as AlertDialogOverlay, + Content as AlertDialogContent, + Description as AlertDialogDescription, +}; diff --git a/apps/main/src/lib/components/ui/alert/alert-description.svelte b/apps/main/src/lib/components/ui/alert/alert-description.svelte new file mode 100644 index 0000000..8b56aed --- /dev/null +++ b/apps/main/src/lib/components/ui/alert/alert-description.svelte @@ -0,0 +1,23 @@ + + +
+ {@render children?.()} +
diff --git a/apps/main/src/lib/components/ui/alert/alert-title.svelte b/apps/main/src/lib/components/ui/alert/alert-title.svelte new file mode 100644 index 0000000..77e45ad --- /dev/null +++ b/apps/main/src/lib/components/ui/alert/alert-title.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/apps/main/src/lib/components/ui/alert/alert.svelte b/apps/main/src/lib/components/ui/alert/alert.svelte new file mode 100644 index 0000000..2b2eff9 --- /dev/null +++ b/apps/main/src/lib/components/ui/alert/alert.svelte @@ -0,0 +1,44 @@ + + + + + diff --git a/apps/main/src/lib/components/ui/alert/index.ts b/apps/main/src/lib/components/ui/alert/index.ts new file mode 100644 index 0000000..97e21b4 --- /dev/null +++ b/apps/main/src/lib/components/ui/alert/index.ts @@ -0,0 +1,14 @@ +import Root from "./alert.svelte"; +import Description from "./alert-description.svelte"; +import Title from "./alert-title.svelte"; +export { alertVariants, type AlertVariant } from "./alert.svelte"; + +export { + Root, + Description, + Title, + // + Root as Alert, + Description as AlertDescription, + Title as AlertTitle, +}; diff --git a/apps/main/src/lib/components/ui/aspect-ratio/aspect-ratio.svelte b/apps/main/src/lib/components/ui/aspect-ratio/aspect-ratio.svelte new file mode 100644 index 0000000..815aab0 --- /dev/null +++ b/apps/main/src/lib/components/ui/aspect-ratio/aspect-ratio.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/aspect-ratio/index.ts b/apps/main/src/lib/components/ui/aspect-ratio/index.ts new file mode 100644 index 0000000..985c75f --- /dev/null +++ b/apps/main/src/lib/components/ui/aspect-ratio/index.ts @@ -0,0 +1,3 @@ +import Root from "./aspect-ratio.svelte"; + +export { Root, Root as AspectRatio }; diff --git a/apps/main/src/lib/components/ui/avatar/avatar-fallback.svelte b/apps/main/src/lib/components/ui/avatar/avatar-fallback.svelte new file mode 100644 index 0000000..249d4a4 --- /dev/null +++ b/apps/main/src/lib/components/ui/avatar/avatar-fallback.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/avatar/avatar-image.svelte b/apps/main/src/lib/components/ui/avatar/avatar-image.svelte new file mode 100644 index 0000000..2bb9db4 --- /dev/null +++ b/apps/main/src/lib/components/ui/avatar/avatar-image.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/avatar/avatar.svelte b/apps/main/src/lib/components/ui/avatar/avatar.svelte new file mode 100644 index 0000000..e37214d --- /dev/null +++ b/apps/main/src/lib/components/ui/avatar/avatar.svelte @@ -0,0 +1,19 @@ + + + diff --git a/apps/main/src/lib/components/ui/avatar/index.ts b/apps/main/src/lib/components/ui/avatar/index.ts new file mode 100644 index 0000000..d06457b --- /dev/null +++ b/apps/main/src/lib/components/ui/avatar/index.ts @@ -0,0 +1,13 @@ +import Root from "./avatar.svelte"; +import Image from "./avatar-image.svelte"; +import Fallback from "./avatar-fallback.svelte"; + +export { + Root, + Image, + Fallback, + // + Root as Avatar, + Image as AvatarImage, + Fallback as AvatarFallback, +}; diff --git a/apps/main/src/lib/components/ui/badge/badge.svelte b/apps/main/src/lib/components/ui/badge/badge.svelte new file mode 100644 index 0000000..e3164ba --- /dev/null +++ b/apps/main/src/lib/components/ui/badge/badge.svelte @@ -0,0 +1,50 @@ + + + + + + {@render children?.()} + diff --git a/apps/main/src/lib/components/ui/badge/index.ts b/apps/main/src/lib/components/ui/badge/index.ts new file mode 100644 index 0000000..64e0aa9 --- /dev/null +++ b/apps/main/src/lib/components/ui/badge/index.ts @@ -0,0 +1,2 @@ +export { default as Badge } from "./badge.svelte"; +export { badgeVariants, type BadgeVariant } from "./badge.svelte"; diff --git a/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte new file mode 100644 index 0000000..a178cf5 --- /dev/null +++ b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte @@ -0,0 +1,23 @@ + + + diff --git a/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte new file mode 100644 index 0000000..1a84c4c --- /dev/null +++ b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte @@ -0,0 +1,20 @@ + + +
  • + {@render children?.()} +
  • diff --git a/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte new file mode 100644 index 0000000..e6bc17d --- /dev/null +++ b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte @@ -0,0 +1,31 @@ + + +{#if child} + {@render child({ props: attrs })} +{:else} + + {@render children?.()} + +{/if} diff --git a/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte new file mode 100644 index 0000000..1272a37 --- /dev/null +++ b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte @@ -0,0 +1,23 @@ + + +
      + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte new file mode 100644 index 0000000..5fb6979 --- /dev/null +++ b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte @@ -0,0 +1,23 @@ + + + + {@render children?.()} + diff --git a/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte new file mode 100644 index 0000000..84106a1 --- /dev/null +++ b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte @@ -0,0 +1,27 @@ + + + diff --git a/apps/main/src/lib/components/ui/breadcrumb/breadcrumb.svelte b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb.svelte new file mode 100644 index 0000000..8f8a3e6 --- /dev/null +++ b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb.svelte @@ -0,0 +1,21 @@ + + + diff --git a/apps/main/src/lib/components/ui/breadcrumb/index.ts b/apps/main/src/lib/components/ui/breadcrumb/index.ts new file mode 100644 index 0000000..dc914ec --- /dev/null +++ b/apps/main/src/lib/components/ui/breadcrumb/index.ts @@ -0,0 +1,25 @@ +import Root from "./breadcrumb.svelte"; +import Ellipsis from "./breadcrumb-ellipsis.svelte"; +import Item from "./breadcrumb-item.svelte"; +import Separator from "./breadcrumb-separator.svelte"; +import Link from "./breadcrumb-link.svelte"; +import List from "./breadcrumb-list.svelte"; +import Page from "./breadcrumb-page.svelte"; + +export { + Root, + Ellipsis, + Item, + Separator, + Link, + List, + Page, + // + Root as Breadcrumb, + Ellipsis as BreadcrumbEllipsis, + Item as BreadcrumbItem, + Separator as BreadcrumbSeparator, + Link as BreadcrumbLink, + List as BreadcrumbList, + Page as BreadcrumbPage, +}; diff --git a/apps/main/src/lib/components/ui/button-group/button-group-separator.svelte b/apps/main/src/lib/components/ui/button-group/button-group-separator.svelte new file mode 100644 index 0000000..86ff8ae --- /dev/null +++ b/apps/main/src/lib/components/ui/button-group/button-group-separator.svelte @@ -0,0 +1,20 @@ + + + diff --git a/apps/main/src/lib/components/ui/button-group/button-group-text.svelte b/apps/main/src/lib/components/ui/button-group/button-group-text.svelte new file mode 100644 index 0000000..1be72bb --- /dev/null +++ b/apps/main/src/lib/components/ui/button-group/button-group-text.svelte @@ -0,0 +1,30 @@ + + +{#if child} + {@render child({ props: mergedProps })} +{:else} +
    + {@render mergedProps.children?.()} +
    +{/if} diff --git a/apps/main/src/lib/components/ui/button-group/button-group.svelte b/apps/main/src/lib/components/ui/button-group/button-group.svelte new file mode 100644 index 0000000..34c8d79 --- /dev/null +++ b/apps/main/src/lib/components/ui/button-group/button-group.svelte @@ -0,0 +1,46 @@ + + + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/button-group/index.ts b/apps/main/src/lib/components/ui/button-group/index.ts new file mode 100644 index 0000000..7f706e3 --- /dev/null +++ b/apps/main/src/lib/components/ui/button-group/index.ts @@ -0,0 +1,13 @@ +import Root from "./button-group.svelte"; +import Text from "./button-group-text.svelte"; +import Separator from "./button-group-separator.svelte"; + +export { + Root, + Text, + Separator, + // + Root as ButtonGroup, + Text as ButtonGroupText, + Separator as ButtonGroupSeparator, +}; diff --git a/apps/main/src/lib/components/ui/button/button.svelte b/apps/main/src/lib/components/ui/button/button.svelte new file mode 100644 index 0000000..a8296ae --- /dev/null +++ b/apps/main/src/lib/components/ui/button/button.svelte @@ -0,0 +1,82 @@ + + + + +{#if href} + + {@render children?.()} + +{:else} + +{/if} diff --git a/apps/main/src/lib/components/ui/button/index.ts b/apps/main/src/lib/components/ui/button/index.ts new file mode 100644 index 0000000..fb585d7 --- /dev/null +++ b/apps/main/src/lib/components/ui/button/index.ts @@ -0,0 +1,17 @@ +import Root, { + type ButtonProps, + type ButtonSize, + type ButtonVariant, + buttonVariants, +} from "./button.svelte"; + +export { + Root, + type ButtonProps as Props, + // + Root as Button, + buttonVariants, + type ButtonProps, + type ButtonSize, + type ButtonVariant, +}; diff --git a/apps/main/src/lib/components/ui/calendar/calendar-caption.svelte b/apps/main/src/lib/components/ui/calendar/calendar-caption.svelte new file mode 100644 index 0000000..5c93037 --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-caption.svelte @@ -0,0 +1,76 @@ + + +{#snippet MonthSelect()} + { + if (!placeholder) return; + const v = Number.parseInt(e.currentTarget.value); + const newPlaceholder = placeholder.set({ month: v }); + placeholder = newPlaceholder.subtract({ months: monthIndex }); + }} + /> +{/snippet} + +{#snippet YearSelect()} + +{/snippet} + +{#if captionLayout === "dropdown"} + {@render MonthSelect()} + {@render YearSelect()} +{:else if captionLayout === "dropdown-months"} + {@render MonthSelect()} + {#if placeholder} + {formatYear(placeholder)} + {/if} +{:else if captionLayout === "dropdown-years"} + {#if placeholder} + {formatMonth(placeholder)} + {/if} + {@render YearSelect()} +{:else} + {formatMonth(month)} {formatYear(month)} +{/if} diff --git a/apps/main/src/lib/components/ui/calendar/calendar-cell.svelte b/apps/main/src/lib/components/ui/calendar/calendar-cell.svelte new file mode 100644 index 0000000..4cdb548 --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-cell.svelte @@ -0,0 +1,19 @@ + + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar-day.svelte b/apps/main/src/lib/components/ui/calendar/calendar-day.svelte new file mode 100644 index 0000000..19d7bde --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-day.svelte @@ -0,0 +1,35 @@ + + +span]:text-xs [&>span]:opacity-70", + className + )} + {...restProps} +/> diff --git a/apps/main/src/lib/components/ui/calendar/calendar-grid-body.svelte b/apps/main/src/lib/components/ui/calendar/calendar-grid-body.svelte new file mode 100644 index 0000000..8cd86de --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-grid-body.svelte @@ -0,0 +1,12 @@ + + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar-grid-head.svelte b/apps/main/src/lib/components/ui/calendar/calendar-grid-head.svelte new file mode 100644 index 0000000..333edc4 --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-grid-head.svelte @@ -0,0 +1,12 @@ + + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar-grid-row.svelte b/apps/main/src/lib/components/ui/calendar/calendar-grid-row.svelte new file mode 100644 index 0000000..9032236 --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-grid-row.svelte @@ -0,0 +1,12 @@ + + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar-grid.svelte b/apps/main/src/lib/components/ui/calendar/calendar-grid.svelte new file mode 100644 index 0000000..e0c8627 --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-grid.svelte @@ -0,0 +1,16 @@ + + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar-head-cell.svelte b/apps/main/src/lib/components/ui/calendar/calendar-head-cell.svelte new file mode 100644 index 0000000..131807e --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-head-cell.svelte @@ -0,0 +1,19 @@ + + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar-header.svelte b/apps/main/src/lib/components/ui/calendar/calendar-header.svelte new file mode 100644 index 0000000..c39e955 --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-header.svelte @@ -0,0 +1,19 @@ + + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar-heading.svelte b/apps/main/src/lib/components/ui/calendar/calendar-heading.svelte new file mode 100644 index 0000000..a9b9810 --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-heading.svelte @@ -0,0 +1,16 @@ + + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar-month-select.svelte b/apps/main/src/lib/components/ui/calendar/calendar-month-select.svelte new file mode 100644 index 0000000..8d88deb --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-month-select.svelte @@ -0,0 +1,44 @@ + + + + + {#snippet child({ props, monthItems, selectedMonthItem })} + + + {/snippet} + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar-month.svelte b/apps/main/src/lib/components/ui/calendar/calendar-month.svelte new file mode 100644 index 0000000..e747fae --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-month.svelte @@ -0,0 +1,15 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/calendar/calendar-months.svelte b/apps/main/src/lib/components/ui/calendar/calendar-months.svelte new file mode 100644 index 0000000..f717a9d --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-months.svelte @@ -0,0 +1,19 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/calendar/calendar-nav.svelte b/apps/main/src/lib/components/ui/calendar/calendar-nav.svelte new file mode 100644 index 0000000..27f33d7 --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-nav.svelte @@ -0,0 +1,19 @@ + + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar-next-button.svelte b/apps/main/src/lib/components/ui/calendar/calendar-next-button.svelte new file mode 100644 index 0000000..5c5a78d --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-next-button.svelte @@ -0,0 +1,31 @@ + + +{#snippet Fallback()} + +{/snippet} + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar-prev-button.svelte b/apps/main/src/lib/components/ui/calendar/calendar-prev-button.svelte new file mode 100644 index 0000000..33cfd63 --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-prev-button.svelte @@ -0,0 +1,31 @@ + + +{#snippet Fallback()} + +{/snippet} + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar-year-select.svelte b/apps/main/src/lib/components/ui/calendar/calendar-year-select.svelte new file mode 100644 index 0000000..226efdf --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-year-select.svelte @@ -0,0 +1,43 @@ + + + + + {#snippet child({ props, yearItems, selectedYearItem })} + + + {/snippet} + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar.svelte b/apps/main/src/lib/components/ui/calendar/calendar.svelte new file mode 100644 index 0000000..29b6fff --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar.svelte @@ -0,0 +1,115 @@ + + + + + {#snippet children({ months, weekdays })} + + + + + + {#each months as month, monthIndex (month)} + + + + + + + + {#each weekdays as weekday (weekday)} + + {weekday.slice(0, 2)} + + {/each} + + + + {#each month.weeks as weekDates (weekDates)} + + {#each weekDates as date (date)} + + {#if day} + {@render day({ + day: date, + outsideMonth: !isEqualMonth(date, month.value), + })} + {:else} + + {/if} + + {/each} + + {/each} + + + + {/each} + + {/snippet} + diff --git a/apps/main/src/lib/components/ui/calendar/index.ts b/apps/main/src/lib/components/ui/calendar/index.ts new file mode 100644 index 0000000..f3a16d2 --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/index.ts @@ -0,0 +1,40 @@ +import Root from "./calendar.svelte"; +import Cell from "./calendar-cell.svelte"; +import Day from "./calendar-day.svelte"; +import Grid from "./calendar-grid.svelte"; +import Header from "./calendar-header.svelte"; +import Months from "./calendar-months.svelte"; +import GridRow from "./calendar-grid-row.svelte"; +import Heading from "./calendar-heading.svelte"; +import GridBody from "./calendar-grid-body.svelte"; +import GridHead from "./calendar-grid-head.svelte"; +import HeadCell from "./calendar-head-cell.svelte"; +import NextButton from "./calendar-next-button.svelte"; +import PrevButton from "./calendar-prev-button.svelte"; +import MonthSelect from "./calendar-month-select.svelte"; +import YearSelect from "./calendar-year-select.svelte"; +import Month from "./calendar-month.svelte"; +import Nav from "./calendar-nav.svelte"; +import Caption from "./calendar-caption.svelte"; + +export { + Day, + Cell, + Grid, + Header, + Months, + GridRow, + Heading, + GridBody, + GridHead, + HeadCell, + NextButton, + PrevButton, + Nav, + Month, + YearSelect, + MonthSelect, + Caption, + // + Root as Calendar, +}; diff --git a/apps/main/src/lib/components/ui/card/card-action.svelte b/apps/main/src/lib/components/ui/card/card-action.svelte new file mode 100644 index 0000000..cc36c56 --- /dev/null +++ b/apps/main/src/lib/components/ui/card/card-action.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/card/card-content.svelte b/apps/main/src/lib/components/ui/card/card-content.svelte new file mode 100644 index 0000000..bc90b83 --- /dev/null +++ b/apps/main/src/lib/components/ui/card/card-content.svelte @@ -0,0 +1,15 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/card/card-description.svelte b/apps/main/src/lib/components/ui/card/card-description.svelte new file mode 100644 index 0000000..9b20ac7 --- /dev/null +++ b/apps/main/src/lib/components/ui/card/card-description.svelte @@ -0,0 +1,20 @@ + + +

    + {@render children?.()} +

    diff --git a/apps/main/src/lib/components/ui/card/card-footer.svelte b/apps/main/src/lib/components/ui/card/card-footer.svelte new file mode 100644 index 0000000..2d4d0f2 --- /dev/null +++ b/apps/main/src/lib/components/ui/card/card-footer.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/card/card-header.svelte b/apps/main/src/lib/components/ui/card/card-header.svelte new file mode 100644 index 0000000..2501788 --- /dev/null +++ b/apps/main/src/lib/components/ui/card/card-header.svelte @@ -0,0 +1,23 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/card/card-title.svelte b/apps/main/src/lib/components/ui/card/card-title.svelte new file mode 100644 index 0000000..7447231 --- /dev/null +++ b/apps/main/src/lib/components/ui/card/card-title.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/card/card.svelte b/apps/main/src/lib/components/ui/card/card.svelte new file mode 100644 index 0000000..99448cc --- /dev/null +++ b/apps/main/src/lib/components/ui/card/card.svelte @@ -0,0 +1,23 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/card/index.ts b/apps/main/src/lib/components/ui/card/index.ts new file mode 100644 index 0000000..4d3fce4 --- /dev/null +++ b/apps/main/src/lib/components/ui/card/index.ts @@ -0,0 +1,25 @@ +import Root from "./card.svelte"; +import Content from "./card-content.svelte"; +import Description from "./card-description.svelte"; +import Footer from "./card-footer.svelte"; +import Header from "./card-header.svelte"; +import Title from "./card-title.svelte"; +import Action from "./card-action.svelte"; + +export { + Root, + Content, + Description, + Footer, + Header, + Title, + Action, + // + Root as Card, + Content as CardContent, + Description as CardDescription, + Footer as CardFooter, + Header as CardHeader, + Title as CardTitle, + Action as CardAction, +}; diff --git a/apps/main/src/lib/components/ui/carousel/carousel-content.svelte b/apps/main/src/lib/components/ui/carousel/carousel-content.svelte new file mode 100644 index 0000000..84c71f8 --- /dev/null +++ b/apps/main/src/lib/components/ui/carousel/carousel-content.svelte @@ -0,0 +1,43 @@ + + +
    +
    + {@render children?.()} +
    +
    diff --git a/apps/main/src/lib/components/ui/carousel/carousel-item.svelte b/apps/main/src/lib/components/ui/carousel/carousel-item.svelte new file mode 100644 index 0000000..ebf1649 --- /dev/null +++ b/apps/main/src/lib/components/ui/carousel/carousel-item.svelte @@ -0,0 +1,30 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/carousel/carousel-next.svelte b/apps/main/src/lib/components/ui/carousel/carousel-next.svelte new file mode 100644 index 0000000..1aaa1f4 --- /dev/null +++ b/apps/main/src/lib/components/ui/carousel/carousel-next.svelte @@ -0,0 +1,38 @@ + + + diff --git a/apps/main/src/lib/components/ui/carousel/carousel-previous.svelte b/apps/main/src/lib/components/ui/carousel/carousel-previous.svelte new file mode 100644 index 0000000..dafe4fd --- /dev/null +++ b/apps/main/src/lib/components/ui/carousel/carousel-previous.svelte @@ -0,0 +1,38 @@ + + + diff --git a/apps/main/src/lib/components/ui/carousel/carousel.svelte b/apps/main/src/lib/components/ui/carousel/carousel.svelte new file mode 100644 index 0000000..0492805 --- /dev/null +++ b/apps/main/src/lib/components/ui/carousel/carousel.svelte @@ -0,0 +1,93 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/carousel/context.ts b/apps/main/src/lib/components/ui/carousel/context.ts new file mode 100644 index 0000000..a5fd74f --- /dev/null +++ b/apps/main/src/lib/components/ui/carousel/context.ts @@ -0,0 +1,58 @@ +import type { WithElementRef } from "$lib/utils.js"; +import type { + EmblaCarouselSvelteType, + default as emblaCarouselSvelte, +} from "embla-carousel-svelte"; +import { getContext, hasContext, setContext } from "svelte"; +import type { HTMLAttributes } from "svelte/elements"; + +export type CarouselAPI = + NonNullable["on:emblaInit"]> extends ( + evt: CustomEvent + ) => void + ? CarouselAPI + : never; + +type EmblaCarouselConfig = NonNullable[1]>; + +export type CarouselOptions = EmblaCarouselConfig["options"]; +export type CarouselPlugins = EmblaCarouselConfig["plugins"]; + +//// + +export type CarouselProps = { + opts?: CarouselOptions; + plugins?: CarouselPlugins; + setApi?: (api: CarouselAPI | undefined) => void; + orientation?: "horizontal" | "vertical"; +} & WithElementRef>; + +const EMBLA_CAROUSEL_CONTEXT = Symbol("EMBLA_CAROUSEL_CONTEXT"); + +export type EmblaContext = { + api: CarouselAPI | undefined; + orientation: "horizontal" | "vertical"; + scrollNext: () => void; + scrollPrev: () => void; + canScrollNext: boolean; + canScrollPrev: boolean; + handleKeyDown: (e: KeyboardEvent) => void; + options: CarouselOptions; + plugins: CarouselPlugins; + onInit: (e: CustomEvent) => void; + scrollTo: (index: number, jump?: boolean) => void; + scrollSnaps: number[]; + selectedIndex: number; +}; + +export function setEmblaContext(config: EmblaContext): EmblaContext { + setContext(EMBLA_CAROUSEL_CONTEXT, config); + return config; +} + +export function getEmblaContext(name = "This component") { + if (!hasContext(EMBLA_CAROUSEL_CONTEXT)) { + throw new Error(`${name} must be used within a component`); + } + return getContext>(EMBLA_CAROUSEL_CONTEXT); +} diff --git a/apps/main/src/lib/components/ui/carousel/index.ts b/apps/main/src/lib/components/ui/carousel/index.ts new file mode 100644 index 0000000..957fc74 --- /dev/null +++ b/apps/main/src/lib/components/ui/carousel/index.ts @@ -0,0 +1,19 @@ +import Root from "./carousel.svelte"; +import Content from "./carousel-content.svelte"; +import Item from "./carousel-item.svelte"; +import Previous from "./carousel-previous.svelte"; +import Next from "./carousel-next.svelte"; + +export { + Root, + Content, + Item, + Previous, + Next, + // + Root as Carousel, + Content as CarouselContent, + Item as CarouselItem, + Previous as CarouselPrevious, + Next as CarouselNext, +}; diff --git a/apps/main/src/lib/components/ui/chart/chart-container.svelte b/apps/main/src/lib/components/ui/chart/chart-container.svelte new file mode 100644 index 0000000..36c0000 --- /dev/null +++ b/apps/main/src/lib/components/ui/chart/chart-container.svelte @@ -0,0 +1,80 @@ + + +
    + + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/chart/chart-style.svelte b/apps/main/src/lib/components/ui/chart/chart-style.svelte new file mode 100644 index 0000000..864ecc3 --- /dev/null +++ b/apps/main/src/lib/components/ui/chart/chart-style.svelte @@ -0,0 +1,37 @@ + + +{#if themeContents} + {#key id} + + {themeContents} + + {/key} +{/if} diff --git a/apps/main/src/lib/components/ui/chart/chart-tooltip.svelte b/apps/main/src/lib/components/ui/chart/chart-tooltip.svelte new file mode 100644 index 0000000..6eb66ff --- /dev/null +++ b/apps/main/src/lib/components/ui/chart/chart-tooltip.svelte @@ -0,0 +1,159 @@ + + +{#snippet TooltipLabel()} + {#if formattedLabel} +
    + {#if typeof formattedLabel === "function"} + {@render formattedLabel()} + {:else} + {formattedLabel} + {/if} +
    + {/if} +{/snippet} + + +
    + {#if !nestLabel} + {@render TooltipLabel()} + {/if} +
    + {#each tooltipCtx.payload as item, i (item.key + i)} + {@const key = `${nameKey || item.key || item.name || "value"}`} + {@const itemConfig = getPayloadConfigFromPayload(chart.config, item, key)} + {@const indicatorColor = color || item.payload?.color || item.color} +
    svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:size-2.5", + indicator === "dot" && "items-center" + )} + > + {#if formatter && item.value !== undefined && item.name} + {@render formatter({ + value: item.value, + name: item.name, + item, + index: i, + payload: tooltipCtx.payload, + })} + {:else} + {#if itemConfig?.icon} + + {:else if !hideIndicator} +
    + {/if} +
    +
    + {#if nestLabel} + {@render TooltipLabel()} + {/if} + + {itemConfig?.label || item.name} + +
    + {#if item.value !== undefined} + + {item.value.toLocaleString()} + + {/if} +
    + {/if} +
    + {/each} +
    +
    +
    diff --git a/apps/main/src/lib/components/ui/chart/chart-utils.ts b/apps/main/src/lib/components/ui/chart/chart-utils.ts new file mode 100644 index 0000000..2decbbf --- /dev/null +++ b/apps/main/src/lib/components/ui/chart/chart-utils.ts @@ -0,0 +1,66 @@ +import type { Tooltip } from "layerchart"; +import { getContext, setContext, type Component, type ComponentProps, type Snippet } from "svelte"; + +export const THEMES = { light: "", dark: ".dark" } as const; + +export type ChartConfig = { + [k in string]: { + label?: string; + icon?: Component; + } & ( + | { color?: string; theme?: never } + | { color?: never; theme: Record } + ); +}; + +export type ExtractSnippetParams = T extends Snippet<[infer P]> ? P : never; + +export type TooltipPayload = ExtractSnippetParams< + ComponentProps["children"] +>["payload"][number]; + +// Helper to extract item config from a payload. +export function getPayloadConfigFromPayload( + config: ChartConfig, + payload: TooltipPayload, + key: string +) { + if (typeof payload !== "object" || payload === null) return undefined; + + const payloadPayload = + "payload" in payload && typeof payload.payload === "object" && payload.payload !== null + ? payload.payload + : undefined; + + let configLabelKey: string = key; + + if (payload.key === key) { + configLabelKey = payload.key; + } else if (payload.name === key) { + configLabelKey = payload.name; + } else if (key in payload && typeof payload[key as keyof typeof payload] === "string") { + configLabelKey = payload[key as keyof typeof payload] as string; + } else if ( + payloadPayload !== undefined && + key in payloadPayload && + typeof payloadPayload[key as keyof typeof payloadPayload] === "string" + ) { + configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string; + } + + return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config]; +} + +type ChartContextValue = { + config: ChartConfig; +}; + +const chartContextKey = Symbol("chart-context"); + +export function setChartContext(value: ChartContextValue) { + return setContext(chartContextKey, value); +} + +export function useChart() { + return getContext(chartContextKey); +} diff --git a/apps/main/src/lib/components/ui/chart/index.ts b/apps/main/src/lib/components/ui/chart/index.ts new file mode 100644 index 0000000..f22375e --- /dev/null +++ b/apps/main/src/lib/components/ui/chart/index.ts @@ -0,0 +1,6 @@ +import ChartContainer from "./chart-container.svelte"; +import ChartTooltip from "./chart-tooltip.svelte"; + +export { getPayloadConfigFromPayload, type ChartConfig } from "./chart-utils.js"; + +export { ChartContainer, ChartTooltip, ChartContainer as Container, ChartTooltip as Tooltip }; diff --git a/apps/main/src/lib/components/ui/checkbox/checkbox.svelte b/apps/main/src/lib/components/ui/checkbox/checkbox.svelte new file mode 100644 index 0000000..0a2b010 --- /dev/null +++ b/apps/main/src/lib/components/ui/checkbox/checkbox.svelte @@ -0,0 +1,36 @@ + + + + {#snippet children({ checked, indeterminate })} +
    + {#if checked} + + {:else if indeterminate} + + {/if} +
    + {/snippet} +
    diff --git a/apps/main/src/lib/components/ui/checkbox/index.ts b/apps/main/src/lib/components/ui/checkbox/index.ts new file mode 100644 index 0000000..6d92d94 --- /dev/null +++ b/apps/main/src/lib/components/ui/checkbox/index.ts @@ -0,0 +1,6 @@ +import Root from "./checkbox.svelte"; +export { + Root, + // + Root as Checkbox, +}; diff --git a/apps/main/src/lib/components/ui/collapsible/collapsible-content.svelte b/apps/main/src/lib/components/ui/collapsible/collapsible-content.svelte new file mode 100644 index 0000000..bdabb55 --- /dev/null +++ b/apps/main/src/lib/components/ui/collapsible/collapsible-content.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/collapsible/collapsible-trigger.svelte b/apps/main/src/lib/components/ui/collapsible/collapsible-trigger.svelte new file mode 100644 index 0000000..ece7ad6 --- /dev/null +++ b/apps/main/src/lib/components/ui/collapsible/collapsible-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/collapsible/collapsible.svelte b/apps/main/src/lib/components/ui/collapsible/collapsible.svelte new file mode 100644 index 0000000..39cdd4e --- /dev/null +++ b/apps/main/src/lib/components/ui/collapsible/collapsible.svelte @@ -0,0 +1,11 @@ + + + diff --git a/apps/main/src/lib/components/ui/collapsible/index.ts b/apps/main/src/lib/components/ui/collapsible/index.ts new file mode 100644 index 0000000..169b479 --- /dev/null +++ b/apps/main/src/lib/components/ui/collapsible/index.ts @@ -0,0 +1,13 @@ +import Root from "./collapsible.svelte"; +import Trigger from "./collapsible-trigger.svelte"; +import Content from "./collapsible-content.svelte"; + +export { + Root, + Content, + Trigger, + // + Root as Collapsible, + Content as CollapsibleContent, + Trigger as CollapsibleTrigger, +}; diff --git a/apps/main/src/lib/components/ui/command/command-dialog.svelte b/apps/main/src/lib/components/ui/command/command-dialog.svelte new file mode 100644 index 0000000..4bdb740 --- /dev/null +++ b/apps/main/src/lib/components/ui/command/command-dialog.svelte @@ -0,0 +1,40 @@ + + + + + {title} + {description} + + + + + diff --git a/apps/main/src/lib/components/ui/command/command-empty.svelte b/apps/main/src/lib/components/ui/command/command-empty.svelte new file mode 100644 index 0000000..6726cd8 --- /dev/null +++ b/apps/main/src/lib/components/ui/command/command-empty.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/command/command-group.svelte b/apps/main/src/lib/components/ui/command/command-group.svelte new file mode 100644 index 0000000..104f817 --- /dev/null +++ b/apps/main/src/lib/components/ui/command/command-group.svelte @@ -0,0 +1,32 @@ + + + + {#if heading} + + {heading} + + {/if} + + diff --git a/apps/main/src/lib/components/ui/command/command-input.svelte b/apps/main/src/lib/components/ui/command/command-input.svelte new file mode 100644 index 0000000..28d3dcf --- /dev/null +++ b/apps/main/src/lib/components/ui/command/command-input.svelte @@ -0,0 +1,26 @@ + + +
    + + +
    diff --git a/apps/main/src/lib/components/ui/command/command-item.svelte b/apps/main/src/lib/components/ui/command/command-item.svelte new file mode 100644 index 0000000..5833416 --- /dev/null +++ b/apps/main/src/lib/components/ui/command/command-item.svelte @@ -0,0 +1,20 @@ + + + diff --git a/apps/main/src/lib/components/ui/command/command-link-item.svelte b/apps/main/src/lib/components/ui/command/command-link-item.svelte new file mode 100644 index 0000000..ada6d2c --- /dev/null +++ b/apps/main/src/lib/components/ui/command/command-link-item.svelte @@ -0,0 +1,20 @@ + + + diff --git a/apps/main/src/lib/components/ui/command/command-list.svelte b/apps/main/src/lib/components/ui/command/command-list.svelte new file mode 100644 index 0000000..2d3a01a --- /dev/null +++ b/apps/main/src/lib/components/ui/command/command-list.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/command/command-loading.svelte b/apps/main/src/lib/components/ui/command/command-loading.svelte new file mode 100644 index 0000000..19dd298 --- /dev/null +++ b/apps/main/src/lib/components/ui/command/command-loading.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/command/command-separator.svelte b/apps/main/src/lib/components/ui/command/command-separator.svelte new file mode 100644 index 0000000..35c4c95 --- /dev/null +++ b/apps/main/src/lib/components/ui/command/command-separator.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/command/command-shortcut.svelte b/apps/main/src/lib/components/ui/command/command-shortcut.svelte new file mode 100644 index 0000000..f3d6928 --- /dev/null +++ b/apps/main/src/lib/components/ui/command/command-shortcut.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/apps/main/src/lib/components/ui/command/command.svelte b/apps/main/src/lib/components/ui/command/command.svelte new file mode 100644 index 0000000..a1581f1 --- /dev/null +++ b/apps/main/src/lib/components/ui/command/command.svelte @@ -0,0 +1,28 @@ + + + diff --git a/apps/main/src/lib/components/ui/command/index.ts b/apps/main/src/lib/components/ui/command/index.ts new file mode 100644 index 0000000..5435fbe --- /dev/null +++ b/apps/main/src/lib/components/ui/command/index.ts @@ -0,0 +1,37 @@ +import Root from "./command.svelte"; +import Loading from "./command-loading.svelte"; +import Dialog from "./command-dialog.svelte"; +import Empty from "./command-empty.svelte"; +import Group from "./command-group.svelte"; +import Item from "./command-item.svelte"; +import Input from "./command-input.svelte"; +import List from "./command-list.svelte"; +import Separator from "./command-separator.svelte"; +import Shortcut from "./command-shortcut.svelte"; +import LinkItem from "./command-link-item.svelte"; + +export { + Root, + Dialog, + Empty, + Group, + Item, + LinkItem, + Input, + List, + Separator, + Shortcut, + Loading, + // + Root as Command, + Dialog as CommandDialog, + Empty as CommandEmpty, + Group as CommandGroup, + Item as CommandItem, + LinkItem as CommandLinkItem, + Input as CommandInput, + List as CommandList, + Separator as CommandSeparator, + Shortcut as CommandShortcut, + Loading as CommandLoading, +}; diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-checkbox-item.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-checkbox-item.svelte new file mode 100644 index 0000000..f3b6db3 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-checkbox-item.svelte @@ -0,0 +1,40 @@ + + + + {#snippet children({ checked })} + + {#if checked} + + {/if} + + {@render childrenProp?.()} + {/snippet} + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-content.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-content.svelte new file mode 100644 index 0000000..20b516d --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-content.svelte @@ -0,0 +1,28 @@ + + + + + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-group-heading.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-group-heading.svelte new file mode 100644 index 0000000..2cb6207 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-group-heading.svelte @@ -0,0 +1,21 @@ + + + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-group.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-group.svelte new file mode 100644 index 0000000..c7c1e06 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-group.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-item.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-item.svelte new file mode 100644 index 0000000..4e8d224 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-item.svelte @@ -0,0 +1,27 @@ + + + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-label.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-label.svelte new file mode 100644 index 0000000..5e96107 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-label.svelte @@ -0,0 +1,24 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-portal.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-portal.svelte new file mode 100644 index 0000000..96b1e3e --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-radio-group.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-radio-group.svelte new file mode 100644 index 0000000..964cb55 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-radio-group.svelte @@ -0,0 +1,16 @@ + + + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-radio-item.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-radio-item.svelte new file mode 100644 index 0000000..0141b14 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-radio-item.svelte @@ -0,0 +1,33 @@ + + + + {#snippet children({ checked })} + + {#if checked} + + {/if} + + {@render childrenProp?.({ checked })} + {/snippet} + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-separator.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-separator.svelte new file mode 100644 index 0000000..7f5b237 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-separator.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-shortcut.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-shortcut.svelte new file mode 100644 index 0000000..6181881 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-shortcut.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-sub-content.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-sub-content.svelte new file mode 100644 index 0000000..2b6ca47 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-sub-content.svelte @@ -0,0 +1,20 @@ + + + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-sub-trigger.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-sub-trigger.svelte new file mode 100644 index 0000000..38d74eb --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-sub-trigger.svelte @@ -0,0 +1,29 @@ + + + + {@render children?.()} + + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-sub.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-sub.svelte new file mode 100644 index 0000000..a03827b --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-sub.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-trigger.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-trigger.svelte new file mode 100644 index 0000000..3efa857 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu.svelte new file mode 100644 index 0000000..cfaefb3 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/context-menu/index.ts b/apps/main/src/lib/components/ui/context-menu/index.ts new file mode 100644 index 0000000..cbeaee1 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/index.ts @@ -0,0 +1,52 @@ +import Root from "./context-menu.svelte"; +import Sub from "./context-menu-sub.svelte"; +import Portal from "./context-menu-portal.svelte"; +import Trigger from "./context-menu-trigger.svelte"; +import Group from "./context-menu-group.svelte"; +import RadioGroup from "./context-menu-radio-group.svelte"; +import Item from "./context-menu-item.svelte"; +import GroupHeading from "./context-menu-group-heading.svelte"; +import Content from "./context-menu-content.svelte"; +import Shortcut from "./context-menu-shortcut.svelte"; +import RadioItem from "./context-menu-radio-item.svelte"; +import Separator from "./context-menu-separator.svelte"; +import SubContent from "./context-menu-sub-content.svelte"; +import SubTrigger from "./context-menu-sub-trigger.svelte"; +import CheckboxItem from "./context-menu-checkbox-item.svelte"; +import Label from "./context-menu-label.svelte"; + +export { + Root, + Sub, + Portal, + Item, + GroupHeading, + Label, + Group, + Trigger, + Content, + Shortcut, + Separator, + RadioItem, + SubContent, + SubTrigger, + RadioGroup, + CheckboxItem, + // + Root as ContextMenu, + Sub as ContextMenuSub, + Portal as ContextMenuPortal, + Item as ContextMenuItem, + GroupHeading as ContextMenuGroupHeading, + Group as ContextMenuGroup, + Content as ContextMenuContent, + Trigger as ContextMenuTrigger, + Shortcut as ContextMenuShortcut, + RadioItem as ContextMenuRadioItem, + Separator as ContextMenuSeparator, + RadioGroup as ContextMenuRadioGroup, + SubContent as ContextMenuSubContent, + SubTrigger as ContextMenuSubTrigger, + CheckboxItem as ContextMenuCheckboxItem, + Label as ContextMenuLabel, +}; diff --git a/apps/main/src/lib/components/ui/data-table/data-table.svelte.ts b/apps/main/src/lib/components/ui/data-table/data-table.svelte.ts new file mode 100644 index 0000000..5b7985e --- /dev/null +++ b/apps/main/src/lib/components/ui/data-table/data-table.svelte.ts @@ -0,0 +1,142 @@ +import { + type RowData, + type TableOptions, + type TableOptionsResolved, + type TableState, + createTable, +} from "@tanstack/table-core"; + +/** + * Creates a reactive TanStack table object for Svelte. + * @param options Table options to create the table with. + * @returns A reactive table object. + * @example + * ```svelte + * + * + * + * + * {#each table.getHeaderGroups() as headerGroup} + * + * {#each headerGroup.headers as header} + * + * {/each} + * + * {/each} + * + * + *
    + * + *
    + * ``` + */ +export function createSvelteTable(options: TableOptions) { + const resolvedOptions: TableOptionsResolved = mergeObjects( + { + state: {}, + onStateChange() {}, + renderFallbackValue: null, + mergeOptions: ( + defaultOptions: TableOptions, + options: Partial> + ) => { + return mergeObjects(defaultOptions, options); + }, + }, + options + ); + + const table = createTable(resolvedOptions); + let state = $state>(table.initialState); + + function updateOptions() { + table.setOptions((prev) => { + return mergeObjects(prev, options, { + state: mergeObjects(state, options.state || {}), + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onStateChange: (updater: any) => { + if (updater instanceof Function) state = updater(state); + else state = mergeObjects(state, updater); + + options.onStateChange?.(updater); + }, + }); + }); + } + + updateOptions(); + + $effect.pre(() => { + updateOptions(); + }); + + return table; +} + +type MaybeThunk = T | (() => T | null | undefined); +type Intersection = (T extends [infer H, ...infer R] + ? H & Intersection + : unknown) & {}; + +/** + * Lazily merges several objects (or thunks) while preserving + * getter semantics from every source. + * + * Proxy-based to avoid known WebKit recursion issue. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function mergeObjects[]>( + ...sources: Sources +): Intersection<{ [K in keyof Sources]: Sources[K] }> { + const resolve = (src: MaybeThunk): T | undefined => + typeof src === "function" ? (src() ?? undefined) : src; + + const findSourceWithKey = (key: PropertyKey) => { + for (let i = sources.length - 1; i >= 0; i--) { + const obj = resolve(sources[i]); + if (obj && key in obj) return obj; + } + return undefined; + }; + + return new Proxy(Object.create(null), { + get(_, key) { + const src = findSourceWithKey(key); + + return src?.[key as never]; + }, + + has(_, key) { + return !!findSourceWithKey(key); + }, + + ownKeys(): (string | symbol)[] { + // eslint-disable-next-line svelte/prefer-svelte-reactivity + const all = new Set(); + for (const s of sources) { + const obj = resolve(s); + if (obj) { + for (const k of Reflect.ownKeys(obj) as (string | symbol)[]) { + all.add(k); + } + } + } + return [...all]; + }, + + getOwnPropertyDescriptor(_, key) { + const src = findSourceWithKey(key); + if (!src) return undefined; + return { + configurable: true, + enumerable: true, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + value: (src as any)[key], + writable: true, + }; + }, + }) as Intersection<{ [K in keyof Sources]: Sources[K] }>; +} diff --git a/apps/main/src/lib/components/ui/data-table/flex-render.svelte b/apps/main/src/lib/components/ui/data-table/flex-render.svelte new file mode 100644 index 0000000..ac82a58 --- /dev/null +++ b/apps/main/src/lib/components/ui/data-table/flex-render.svelte @@ -0,0 +1,40 @@ + + +{#if typeof content === "string"} + {content} +{:else if content instanceof Function} + + + {@const result = content(context as any)} + {#if result instanceof RenderComponentConfig} + {@const { component: Component, props } = result} + + {:else if result instanceof RenderSnippetConfig} + {@const { snippet, params } = result} + {@render snippet({ ...params, attach })} + {:else} + {result} + {/if} +{/if} diff --git a/apps/main/src/lib/components/ui/data-table/index.ts b/apps/main/src/lib/components/ui/data-table/index.ts new file mode 100644 index 0000000..5f4e77e --- /dev/null +++ b/apps/main/src/lib/components/ui/data-table/index.ts @@ -0,0 +1,3 @@ +export { default as FlexRender } from "./flex-render.svelte"; +export { renderComponent, renderSnippet } from "./render-helpers.js"; +export { createSvelteTable } from "./data-table.svelte.js"; diff --git a/apps/main/src/lib/components/ui/data-table/render-helpers.ts b/apps/main/src/lib/components/ui/data-table/render-helpers.ts new file mode 100644 index 0000000..fa036d6 --- /dev/null +++ b/apps/main/src/lib/components/ui/data-table/render-helpers.ts @@ -0,0 +1,111 @@ +import type { Component, ComponentProps, Snippet } from "svelte"; + +/** + * A helper class to make it easy to identify Svelte components in + * `columnDef.cell` and `columnDef.header` properties. + * + * > NOTE: This class should only be used internally by the adapter. If you're + * reading this and you don't know what this is for, you probably don't need it. + * + * @example + * ```svelte + * {@const result = content(context as any)} + * {#if result instanceof RenderComponentConfig} + * {@const { component: Component, props } = result} + * + * {/if} + * ``` + */ +export class RenderComponentConfig { + component: TComponent; + props: ComponentProps | Record; + constructor( + component: TComponent, + props: ComponentProps | Record = {} + ) { + this.component = component; + this.props = props; + } +} + +/** + * A helper class to make it easy to identify Svelte Snippets in `columnDef.cell` and `columnDef.header` properties. + * + * > NOTE: This class should only be used internally by the adapter. If you're + * reading this and you don't know what this is for, you probably don't need it. + * + * @example + * ```svelte + * {@const result = content(context as any)} + * {#if result instanceof RenderSnippetConfig} + * {@const { snippet, params } = result} + * {@render snippet(params)} + * {/if} + * ``` + */ +export class RenderSnippetConfig { + snippet: Snippet<[TProps]>; + params: TProps; + constructor(snippet: Snippet<[TProps]>, params: TProps) { + this.snippet = snippet; + this.params = params; + } +} + +/** + * A helper function to help create cells from Svelte components through ColumnDef's `cell` and `header` properties. + * + * This is only to be used with Svelte Components - use `renderSnippet` for Svelte Snippets. + * + * @param component A Svelte component + * @param props The props to pass to `component` + * @returns A `RenderComponentConfig` object that helps svelte-table know how to render the header/cell component. + * @example + * ```ts + * // +page.svelte + * const defaultColumns = [ + * columnHelper.accessor('name', { + * header: header => renderComponent(SortHeader, { label: 'Name', header }), + * }), + * columnHelper.accessor('state', { + * header: header => renderComponent(SortHeader, { label: 'State', header }), + * }), + * ] + * ``` + * @see {@link https://tanstack.com/table/latest/docs/guide/column-defs} + */ +export function renderComponent< + // eslint-disable-next-line @typescript-eslint/no-explicit-any + T extends Component, + Props extends ComponentProps, +>(component: T, props: Props = {} as Props) { + return new RenderComponentConfig(component, props); +} + +/** + * A helper function to help create cells from Svelte Snippets through ColumnDef's `cell` and `header` properties. + * + * The snippet must only take one parameter. + * + * This is only to be used with Snippets - use `renderComponent` for Svelte Components. + * + * @param snippet + * @param params + * @returns - A `RenderSnippetConfig` object that helps svelte-table know how to render the header/cell snippet. + * @example + * ```ts + * // +page.svelte + * const defaultColumns = [ + * columnHelper.accessor('name', { + * cell: cell => renderSnippet(nameSnippet, { name: cell.row.name }), + * }), + * columnHelper.accessor('state', { + * cell: cell => renderSnippet(stateSnippet, { state: cell.row.state }), + * }), + * ] + * ``` + * @see {@link https://tanstack.com/table/latest/docs/guide/column-defs} + */ +export function renderSnippet(snippet: Snippet<[TProps]>, params: TProps = {} as TProps) { + return new RenderSnippetConfig(snippet, params); +} diff --git a/apps/main/src/lib/components/ui/dialog/dialog-close.svelte b/apps/main/src/lib/components/ui/dialog/dialog-close.svelte new file mode 100644 index 0000000..840b2f6 --- /dev/null +++ b/apps/main/src/lib/components/ui/dialog/dialog-close.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/dialog/dialog-content.svelte b/apps/main/src/lib/components/ui/dialog/dialog-content.svelte new file mode 100644 index 0000000..ae1a03f --- /dev/null +++ b/apps/main/src/lib/components/ui/dialog/dialog-content.svelte @@ -0,0 +1,45 @@ + + + + + + {@render children?.()} + {#if showCloseButton} + + + Close + + {/if} + + diff --git a/apps/main/src/lib/components/ui/dialog/dialog-description.svelte b/apps/main/src/lib/components/ui/dialog/dialog-description.svelte new file mode 100644 index 0000000..3845023 --- /dev/null +++ b/apps/main/src/lib/components/ui/dialog/dialog-description.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/dialog/dialog-footer.svelte b/apps/main/src/lib/components/ui/dialog/dialog-footer.svelte new file mode 100644 index 0000000..e7ff446 --- /dev/null +++ b/apps/main/src/lib/components/ui/dialog/dialog-footer.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/dialog/dialog-header.svelte b/apps/main/src/lib/components/ui/dialog/dialog-header.svelte new file mode 100644 index 0000000..4e5c447 --- /dev/null +++ b/apps/main/src/lib/components/ui/dialog/dialog-header.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/dialog/dialog-overlay.svelte b/apps/main/src/lib/components/ui/dialog/dialog-overlay.svelte new file mode 100644 index 0000000..f81ad83 --- /dev/null +++ b/apps/main/src/lib/components/ui/dialog/dialog-overlay.svelte @@ -0,0 +1,20 @@ + + + diff --git a/apps/main/src/lib/components/ui/dialog/dialog-portal.svelte b/apps/main/src/lib/components/ui/dialog/dialog-portal.svelte new file mode 100644 index 0000000..ccfa79c --- /dev/null +++ b/apps/main/src/lib/components/ui/dialog/dialog-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/dialog/dialog-title.svelte b/apps/main/src/lib/components/ui/dialog/dialog-title.svelte new file mode 100644 index 0000000..e4d4b34 --- /dev/null +++ b/apps/main/src/lib/components/ui/dialog/dialog-title.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/dialog/dialog-trigger.svelte b/apps/main/src/lib/components/ui/dialog/dialog-trigger.svelte new file mode 100644 index 0000000..9d1e801 --- /dev/null +++ b/apps/main/src/lib/components/ui/dialog/dialog-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/dialog/dialog.svelte b/apps/main/src/lib/components/ui/dialog/dialog.svelte new file mode 100644 index 0000000..211672c --- /dev/null +++ b/apps/main/src/lib/components/ui/dialog/dialog.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/dialog/index.ts b/apps/main/src/lib/components/ui/dialog/index.ts new file mode 100644 index 0000000..076cef5 --- /dev/null +++ b/apps/main/src/lib/components/ui/dialog/index.ts @@ -0,0 +1,34 @@ +import Root from "./dialog.svelte"; +import Portal from "./dialog-portal.svelte"; +import Title from "./dialog-title.svelte"; +import Footer from "./dialog-footer.svelte"; +import Header from "./dialog-header.svelte"; +import Overlay from "./dialog-overlay.svelte"; +import Content from "./dialog-content.svelte"; +import Description from "./dialog-description.svelte"; +import Trigger from "./dialog-trigger.svelte"; +import Close from "./dialog-close.svelte"; + +export { + Root, + Title, + Portal, + Footer, + Header, + Trigger, + Overlay, + Content, + Description, + Close, + // + Root as Dialog, + Title as DialogTitle, + Portal as DialogPortal, + Footer as DialogFooter, + Header as DialogHeader, + Trigger as DialogTrigger, + Overlay as DialogOverlay, + Content as DialogContent, + Description as DialogDescription, + Close as DialogClose, +}; diff --git a/apps/main/src/lib/components/ui/drawer/drawer-close.svelte b/apps/main/src/lib/components/ui/drawer/drawer-close.svelte new file mode 100644 index 0000000..95c2479 --- /dev/null +++ b/apps/main/src/lib/components/ui/drawer/drawer-close.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/drawer/drawer-content.svelte b/apps/main/src/lib/components/ui/drawer/drawer-content.svelte new file mode 100644 index 0000000..6bb01db --- /dev/null +++ b/apps/main/src/lib/components/ui/drawer/drawer-content.svelte @@ -0,0 +1,40 @@ + + + + + + + {@render children?.()} + + diff --git a/apps/main/src/lib/components/ui/drawer/drawer-description.svelte b/apps/main/src/lib/components/ui/drawer/drawer-description.svelte new file mode 100644 index 0000000..2763a1a --- /dev/null +++ b/apps/main/src/lib/components/ui/drawer/drawer-description.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/drawer/drawer-footer.svelte b/apps/main/src/lib/components/ui/drawer/drawer-footer.svelte new file mode 100644 index 0000000..1691f58 --- /dev/null +++ b/apps/main/src/lib/components/ui/drawer/drawer-footer.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/drawer/drawer-header.svelte b/apps/main/src/lib/components/ui/drawer/drawer-header.svelte new file mode 100644 index 0000000..65d2de5 --- /dev/null +++ b/apps/main/src/lib/components/ui/drawer/drawer-header.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/drawer/drawer-nested.svelte b/apps/main/src/lib/components/ui/drawer/drawer-nested.svelte new file mode 100644 index 0000000..834af94 --- /dev/null +++ b/apps/main/src/lib/components/ui/drawer/drawer-nested.svelte @@ -0,0 +1,12 @@ + + + diff --git a/apps/main/src/lib/components/ui/drawer/drawer-overlay.svelte b/apps/main/src/lib/components/ui/drawer/drawer-overlay.svelte new file mode 100644 index 0000000..53f78a2 --- /dev/null +++ b/apps/main/src/lib/components/ui/drawer/drawer-overlay.svelte @@ -0,0 +1,20 @@ + + + diff --git a/apps/main/src/lib/components/ui/drawer/drawer-portal.svelte b/apps/main/src/lib/components/ui/drawer/drawer-portal.svelte new file mode 100644 index 0000000..5a0dd74 --- /dev/null +++ b/apps/main/src/lib/components/ui/drawer/drawer-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/drawer/drawer-title.svelte b/apps/main/src/lib/components/ui/drawer/drawer-title.svelte new file mode 100644 index 0000000..a2e5761 --- /dev/null +++ b/apps/main/src/lib/components/ui/drawer/drawer-title.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/drawer/drawer-trigger.svelte b/apps/main/src/lib/components/ui/drawer/drawer-trigger.svelte new file mode 100644 index 0000000..f1877d8 --- /dev/null +++ b/apps/main/src/lib/components/ui/drawer/drawer-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/drawer/drawer.svelte b/apps/main/src/lib/components/ui/drawer/drawer.svelte new file mode 100644 index 0000000..0cb57ff --- /dev/null +++ b/apps/main/src/lib/components/ui/drawer/drawer.svelte @@ -0,0 +1,12 @@ + + + diff --git a/apps/main/src/lib/components/ui/drawer/index.ts b/apps/main/src/lib/components/ui/drawer/index.ts new file mode 100644 index 0000000..1656cac --- /dev/null +++ b/apps/main/src/lib/components/ui/drawer/index.ts @@ -0,0 +1,38 @@ +import Root from "./drawer.svelte"; +import Content from "./drawer-content.svelte"; +import Description from "./drawer-description.svelte"; +import Overlay from "./drawer-overlay.svelte"; +import Footer from "./drawer-footer.svelte"; +import Header from "./drawer-header.svelte"; +import Title from "./drawer-title.svelte"; +import NestedRoot from "./drawer-nested.svelte"; +import Close from "./drawer-close.svelte"; +import Trigger from "./drawer-trigger.svelte"; +import Portal from "./drawer-portal.svelte"; + +export { + Root, + NestedRoot, + Content, + Description, + Overlay, + Footer, + Header, + Title, + Trigger, + Portal, + Close, + + // + Root as Drawer, + NestedRoot as DrawerNestedRoot, + Content as DrawerContent, + Description as DrawerDescription, + Overlay as DrawerOverlay, + Footer as DrawerFooter, + Header as DrawerHeader, + Title as DrawerTitle, + Trigger as DrawerTrigger, + Portal as DrawerPortal, + Close as DrawerClose, +}; diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte new file mode 100644 index 0000000..e0e1971 --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte @@ -0,0 +1,16 @@ + + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte new file mode 100644 index 0000000..6d9ef85 --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte @@ -0,0 +1,43 @@ + + + + {#snippet children({ checked, indeterminate })} + + {#if indeterminate} + + {:else} + + {/if} + + {@render childrenProp?.()} + {/snippet} + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte new file mode 100644 index 0000000..1e96782 --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte @@ -0,0 +1,29 @@ + + + + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte new file mode 100644 index 0000000..433540f --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte @@ -0,0 +1,22 @@ + + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte new file mode 100644 index 0000000..aca1f7b --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte new file mode 100644 index 0000000..04cd110 --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte @@ -0,0 +1,27 @@ + + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte new file mode 100644 index 0000000..9681c2b --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte @@ -0,0 +1,24 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte new file mode 100644 index 0000000..274cfef --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte new file mode 100644 index 0000000..189aef4 --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte @@ -0,0 +1,16 @@ + + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte new file mode 100644 index 0000000..ce2ad09 --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte @@ -0,0 +1,33 @@ + + + + {#snippet children({ checked })} + + {#if checked} + + {/if} + + {@render childrenProp?.({ checked })} + {/snippet} + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte new file mode 100644 index 0000000..90f1b6f --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte new file mode 100644 index 0000000..7c6e9c6 --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte new file mode 100644 index 0000000..3f06dc4 --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte @@ -0,0 +1,20 @@ + + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte new file mode 100644 index 0000000..5f49d01 --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte @@ -0,0 +1,29 @@ + + + + {@render children?.()} + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte new file mode 100644 index 0000000..f044581 --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte new file mode 100644 index 0000000..cb05344 --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte new file mode 100644 index 0000000..cb4bc62 --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/index.ts b/apps/main/src/lib/components/ui/dropdown-menu/index.ts new file mode 100644 index 0000000..7850c6a --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/index.ts @@ -0,0 +1,54 @@ +import Root from "./dropdown-menu.svelte"; +import Sub from "./dropdown-menu-sub.svelte"; +import CheckboxGroup from "./dropdown-menu-checkbox-group.svelte"; +import CheckboxItem from "./dropdown-menu-checkbox-item.svelte"; +import Content from "./dropdown-menu-content.svelte"; +import Group from "./dropdown-menu-group.svelte"; +import Item from "./dropdown-menu-item.svelte"; +import Label from "./dropdown-menu-label.svelte"; +import RadioGroup from "./dropdown-menu-radio-group.svelte"; +import RadioItem from "./dropdown-menu-radio-item.svelte"; +import Separator from "./dropdown-menu-separator.svelte"; +import Shortcut from "./dropdown-menu-shortcut.svelte"; +import Trigger from "./dropdown-menu-trigger.svelte"; +import SubContent from "./dropdown-menu-sub-content.svelte"; +import SubTrigger from "./dropdown-menu-sub-trigger.svelte"; +import GroupHeading from "./dropdown-menu-group-heading.svelte"; +import Portal from "./dropdown-menu-portal.svelte"; + +export { + CheckboxGroup, + CheckboxItem, + Content, + Portal, + Root as DropdownMenu, + CheckboxGroup as DropdownMenuCheckboxGroup, + CheckboxItem as DropdownMenuCheckboxItem, + Content as DropdownMenuContent, + Portal as DropdownMenuPortal, + Group as DropdownMenuGroup, + Item as DropdownMenuItem, + Label as DropdownMenuLabel, + RadioGroup as DropdownMenuRadioGroup, + RadioItem as DropdownMenuRadioItem, + Separator as DropdownMenuSeparator, + Shortcut as DropdownMenuShortcut, + Sub as DropdownMenuSub, + SubContent as DropdownMenuSubContent, + SubTrigger as DropdownMenuSubTrigger, + Trigger as DropdownMenuTrigger, + GroupHeading as DropdownMenuGroupHeading, + Group, + GroupHeading, + Item, + Label, + RadioGroup, + RadioItem, + Root, + Separator, + Shortcut, + Sub, + SubContent, + SubTrigger, + Trigger, +}; diff --git a/apps/main/src/lib/components/ui/empty/empty-content.svelte b/apps/main/src/lib/components/ui/empty/empty-content.svelte new file mode 100644 index 0000000..f5a9c68 --- /dev/null +++ b/apps/main/src/lib/components/ui/empty/empty-content.svelte @@ -0,0 +1,23 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/empty/empty-description.svelte b/apps/main/src/lib/components/ui/empty/empty-description.svelte new file mode 100644 index 0000000..85a866c --- /dev/null +++ b/apps/main/src/lib/components/ui/empty/empty-description.svelte @@ -0,0 +1,23 @@ + + +
    a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4", + className + )} + {...restProps} +> + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/empty/empty-header.svelte b/apps/main/src/lib/components/ui/empty/empty-header.svelte new file mode 100644 index 0000000..296eaf8 --- /dev/null +++ b/apps/main/src/lib/components/ui/empty/empty-header.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/empty/empty-media.svelte b/apps/main/src/lib/components/ui/empty/empty-media.svelte new file mode 100644 index 0000000..0b4e45d --- /dev/null +++ b/apps/main/src/lib/components/ui/empty/empty-media.svelte @@ -0,0 +1,41 @@ + + + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/empty/empty-title.svelte b/apps/main/src/lib/components/ui/empty/empty-title.svelte new file mode 100644 index 0000000..8c237aa --- /dev/null +++ b/apps/main/src/lib/components/ui/empty/empty-title.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/empty/empty.svelte b/apps/main/src/lib/components/ui/empty/empty.svelte new file mode 100644 index 0000000..4ccf060 --- /dev/null +++ b/apps/main/src/lib/components/ui/empty/empty.svelte @@ -0,0 +1,23 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/empty/index.ts b/apps/main/src/lib/components/ui/empty/index.ts new file mode 100644 index 0000000..ae4c106 --- /dev/null +++ b/apps/main/src/lib/components/ui/empty/index.ts @@ -0,0 +1,22 @@ +import Root from "./empty.svelte"; +import Header from "./empty-header.svelte"; +import Media from "./empty-media.svelte"; +import Title from "./empty-title.svelte"; +import Description from "./empty-description.svelte"; +import Content from "./empty-content.svelte"; + +export { + Root, + Header, + Media, + Title, + Description, + Content, + // + Root as Empty, + Header as EmptyHeader, + Media as EmptyMedia, + Title as EmptyTitle, + Description as EmptyDescription, + Content as EmptyContent, +}; diff --git a/apps/main/src/lib/components/ui/field/field-content.svelte b/apps/main/src/lib/components/ui/field/field-content.svelte new file mode 100644 index 0000000..1b6535b --- /dev/null +++ b/apps/main/src/lib/components/ui/field/field-content.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/field/field-description.svelte b/apps/main/src/lib/components/ui/field/field-description.svelte new file mode 100644 index 0000000..a0c9f06 --- /dev/null +++ b/apps/main/src/lib/components/ui/field/field-description.svelte @@ -0,0 +1,25 @@ + + +

    a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4", + className + )} + {...restProps} +> + {@render children?.()} +

    diff --git a/apps/main/src/lib/components/ui/field/field-error.svelte b/apps/main/src/lib/components/ui/field/field-error.svelte new file mode 100644 index 0000000..1d5cc5f --- /dev/null +++ b/apps/main/src/lib/components/ui/field/field-error.svelte @@ -0,0 +1,58 @@ + + +{#if hasContent} + +{/if} diff --git a/apps/main/src/lib/components/ui/field/field-group.svelte b/apps/main/src/lib/components/ui/field/field-group.svelte new file mode 100644 index 0000000..e685427 --- /dev/null +++ b/apps/main/src/lib/components/ui/field/field-group.svelte @@ -0,0 +1,23 @@ + + +
    [data-slot=field-group]]:gap-4", + className + )} + {...restProps} +> + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/field/field-label.svelte b/apps/main/src/lib/components/ui/field/field-label.svelte new file mode 100644 index 0000000..2ee431a --- /dev/null +++ b/apps/main/src/lib/components/ui/field/field-label.svelte @@ -0,0 +1,26 @@ + + + diff --git a/apps/main/src/lib/components/ui/field/field-legend.svelte b/apps/main/src/lib/components/ui/field/field-legend.svelte new file mode 100644 index 0000000..3f1c50f --- /dev/null +++ b/apps/main/src/lib/components/ui/field/field-legend.svelte @@ -0,0 +1,29 @@ + + + + {@render children?.()} + diff --git a/apps/main/src/lib/components/ui/field/field-separator.svelte b/apps/main/src/lib/components/ui/field/field-separator.svelte new file mode 100644 index 0000000..12bcb77 --- /dev/null +++ b/apps/main/src/lib/components/ui/field/field-separator.svelte @@ -0,0 +1,38 @@ + + +
    + + {#if children} + + {@render children()} + + {/if} +
    diff --git a/apps/main/src/lib/components/ui/field/field-set.svelte b/apps/main/src/lib/components/ui/field/field-set.svelte new file mode 100644 index 0000000..1d8e233 --- /dev/null +++ b/apps/main/src/lib/components/ui/field/field-set.svelte @@ -0,0 +1,24 @@ + + +
    [data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3", + className + )} + {...restProps} +> + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/field/field-title.svelte b/apps/main/src/lib/components/ui/field/field-title.svelte new file mode 100644 index 0000000..5906044 --- /dev/null +++ b/apps/main/src/lib/components/ui/field/field-title.svelte @@ -0,0 +1,23 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/field/field.svelte b/apps/main/src/lib/components/ui/field/field.svelte new file mode 100644 index 0000000..3284203 --- /dev/null +++ b/apps/main/src/lib/components/ui/field/field.svelte @@ -0,0 +1,53 @@ + + + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/field/index.ts b/apps/main/src/lib/components/ui/field/index.ts new file mode 100644 index 0000000..a644a95 --- /dev/null +++ b/apps/main/src/lib/components/ui/field/index.ts @@ -0,0 +1,33 @@ +import Field from "./field.svelte"; +import Set from "./field-set.svelte"; +import Legend from "./field-legend.svelte"; +import Group from "./field-group.svelte"; +import Content from "./field-content.svelte"; +import Label from "./field-label.svelte"; +import Title from "./field-title.svelte"; +import Description from "./field-description.svelte"; +import Separator from "./field-separator.svelte"; +import Error from "./field-error.svelte"; + +export { + Field, + Set, + Legend, + Group, + Content, + Label, + Title, + Description, + Separator, + Error, + // + Set as FieldSet, + Legend as FieldLegend, + Group as FieldGroup, + Content as FieldContent, + Label as FieldLabel, + Title as FieldTitle, + Description as FieldDescription, + Separator as FieldSeparator, + Error as FieldError, +}; diff --git a/apps/main/src/lib/components/ui/form/form-button.svelte b/apps/main/src/lib/components/ui/form/form-button.svelte new file mode 100644 index 0000000..48d3936 --- /dev/null +++ b/apps/main/src/lib/components/ui/form/form-button.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/input-group/input-group-input.svelte b/apps/main/src/lib/components/ui/input-group/input-group-input.svelte new file mode 100644 index 0000000..ded2655 --- /dev/null +++ b/apps/main/src/lib/components/ui/input-group/input-group-input.svelte @@ -0,0 +1,23 @@ + + + diff --git a/apps/main/src/lib/components/ui/input-group/input-group-text.svelte b/apps/main/src/lib/components/ui/input-group/input-group-text.svelte new file mode 100644 index 0000000..9c43dc4 --- /dev/null +++ b/apps/main/src/lib/components/ui/input-group/input-group-text.svelte @@ -0,0 +1,22 @@ + + + + {@render children?.()} + diff --git a/apps/main/src/lib/components/ui/input-group/input-group-textarea.svelte b/apps/main/src/lib/components/ui/input-group/input-group-textarea.svelte new file mode 100644 index 0000000..91850ff --- /dev/null +++ b/apps/main/src/lib/components/ui/input-group/input-group-textarea.svelte @@ -0,0 +1,23 @@ + + + diff --git a/apps/main/src/lib/components/ui/toggle-group/index.ts b/apps/main/src/lib/components/ui/toggle-group/index.ts new file mode 100644 index 0000000..12b14b9 --- /dev/null +++ b/apps/main/src/lib/components/ui/toggle-group/index.ts @@ -0,0 +1,10 @@ +import Root from "./toggle-group.svelte"; +import Item from "./toggle-group-item.svelte"; + +export { + Root, + Item, + // + Root as ToggleGroup, + Item as ToggleGroupItem, +}; diff --git a/apps/main/src/lib/components/ui/toggle-group/toggle-group-item.svelte b/apps/main/src/lib/components/ui/toggle-group/toggle-group-item.svelte new file mode 100644 index 0000000..6d60b52 --- /dev/null +++ b/apps/main/src/lib/components/ui/toggle-group/toggle-group-item.svelte @@ -0,0 +1,35 @@ + + + diff --git a/apps/main/src/lib/components/ui/toggle-group/toggle-group.svelte b/apps/main/src/lib/components/ui/toggle-group/toggle-group.svelte new file mode 100644 index 0000000..106561c --- /dev/null +++ b/apps/main/src/lib/components/ui/toggle-group/toggle-group.svelte @@ -0,0 +1,59 @@ + + + + + + diff --git a/apps/main/src/lib/components/ui/toggle/index.ts b/apps/main/src/lib/components/ui/toggle/index.ts new file mode 100644 index 0000000..8cb2936 --- /dev/null +++ b/apps/main/src/lib/components/ui/toggle/index.ts @@ -0,0 +1,13 @@ +import Root from "./toggle.svelte"; +export { + toggleVariants, + type ToggleSize, + type ToggleVariant, + type ToggleVariants, +} from "./toggle.svelte"; + +export { + Root, + // + Root as Toggle, +}; diff --git a/apps/main/src/lib/components/ui/toggle/toggle.svelte b/apps/main/src/lib/components/ui/toggle/toggle.svelte new file mode 100644 index 0000000..56eb86b --- /dev/null +++ b/apps/main/src/lib/components/ui/toggle/toggle.svelte @@ -0,0 +1,52 @@ + + + + + diff --git a/apps/main/src/lib/components/ui/tooltip/index.ts b/apps/main/src/lib/components/ui/tooltip/index.ts new file mode 100644 index 0000000..1718604 --- /dev/null +++ b/apps/main/src/lib/components/ui/tooltip/index.ts @@ -0,0 +1,19 @@ +import Root from "./tooltip.svelte"; +import Trigger from "./tooltip-trigger.svelte"; +import Content from "./tooltip-content.svelte"; +import Provider from "./tooltip-provider.svelte"; +import Portal from "./tooltip-portal.svelte"; + +export { + Root, + Trigger, + Content, + Provider, + Portal, + // + Root as Tooltip, + Content as TooltipContent, + Trigger as TooltipTrigger, + Provider as TooltipProvider, + Portal as TooltipPortal, +}; diff --git a/apps/main/src/lib/components/ui/tooltip/tooltip-content.svelte b/apps/main/src/lib/components/ui/tooltip/tooltip-content.svelte new file mode 100644 index 0000000..788ec34 --- /dev/null +++ b/apps/main/src/lib/components/ui/tooltip/tooltip-content.svelte @@ -0,0 +1,52 @@ + + + + + {@render children?.()} + + {#snippet child({ props })} +
    + {/snippet} +
    +
    +
    diff --git a/apps/main/src/lib/components/ui/tooltip/tooltip-portal.svelte b/apps/main/src/lib/components/ui/tooltip/tooltip-portal.svelte new file mode 100644 index 0000000..d234f7d --- /dev/null +++ b/apps/main/src/lib/components/ui/tooltip/tooltip-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/tooltip/tooltip-provider.svelte b/apps/main/src/lib/components/ui/tooltip/tooltip-provider.svelte new file mode 100644 index 0000000..8150bef --- /dev/null +++ b/apps/main/src/lib/components/ui/tooltip/tooltip-provider.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/tooltip/tooltip-trigger.svelte b/apps/main/src/lib/components/ui/tooltip/tooltip-trigger.svelte new file mode 100644 index 0000000..1acdaa4 --- /dev/null +++ b/apps/main/src/lib/components/ui/tooltip/tooltip-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/tooltip/tooltip.svelte b/apps/main/src/lib/components/ui/tooltip/tooltip.svelte new file mode 100644 index 0000000..0b0f9ce --- /dev/null +++ b/apps/main/src/lib/components/ui/tooltip/tooltip.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/core/constants.ts b/apps/main/src/lib/core/constants.ts new file mode 100644 index 0000000..dad2799 --- /dev/null +++ b/apps/main/src/lib/core/constants.ts @@ -0,0 +1,50 @@ +import LayoutDashboard from "@lucide/svelte/icons/layout-dashboard"; +import { BellRingIcon, UsersIcon } from "@lucide/svelte"; +import UserCircle from "~icons/lucide/user-circle"; + +export type AppSidebarItem = { + title: string; + url: string; + icon?: any; + isActive?: boolean; + items?: { + title: string; + url: string; + }[]; +}; + +export const mainNavTree = [ + { + title: "Dashboard", + url: "/dashboard", + icon: LayoutDashboard, + isActive: true, + }, + { + title: "Users", + url: "/users", + icon: UsersIcon, + }, +] as AppSidebarItem[]; + +export const secondaryNavTree = [ + { + title: "Account", + url: "/account", + icon: UserCircle, + }, + { + title: "Notifications", + url: "/notifications", + icon: BellRingIcon, + }, +] as AppSidebarItem[]; + +export const COMPANY_NAME = "SaaS Template"; +export const WEBSITE_URL = "https://company.com"; + +export const CONTACT_EMAIL = "contact@ahmadrehan.com"; +export const CONTACT_INFO = { email: CONTACT_EMAIL }; + +export const TRANSITION_COLORS = "transition-colors duration-150 ease-in-out"; +export const TRANSITION_ALL = "transition-all duration-150 ease-in-out"; diff --git a/apps/main/src/lib/currency.utils.ts b/apps/main/src/lib/currency.utils.ts new file mode 100644 index 0000000..1dc9712 --- /dev/null +++ b/apps/main/src/lib/currency.utils.ts @@ -0,0 +1,5 @@ +export function formatCurrency(amount: number, currency = "EUR"): string { + return new Intl.NumberFormat("en-US", { style: "currency", currency }).format( + amount, + ); +} diff --git a/apps/main/src/lib/domains/account/account.vm.svelte.ts b/apps/main/src/lib/domains/account/account.vm.svelte.ts new file mode 100644 index 0000000..4c0fa77 --- /dev/null +++ b/apps/main/src/lib/domains/account/account.vm.svelte.ts @@ -0,0 +1,157 @@ +import type { User } from "@pkg/logic/domains/user/data"; +import { authClient } from "$lib/auth.client"; +import { toast } from "svelte-sonner"; +import { ResultAsync, errAsync, okAsync } from "neverthrow"; +import type { Err } from "@pkg/result"; + +class AccountViewModel { + loading = $state(false); + emailLoading = $state(false); + errorMessage = $state(null); + + async updateProfilePicture(imagePath: string): Promise { + const result = await ResultAsync.fromPromise( + authClient.updateUser({ image: imagePath }), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to update profile picture", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ) + .andThen((response) => { + if (response.error) { + return errAsync({ + code: "API_ERROR", + message: + response.error.message ?? + "Failed to update profile picture", + description: + response.error.statusText ?? + "Please try again later", + detail: response.error.statusText ?? "Unknown error", + }); + } + return okAsync(response.data); + }); + + return result.match( + () => { + toast.success("Profile picture updated"); + return true; + }, + (error) => { + this.errorMessage = error.message ?? "Failed to update profile picture"; + toast.error(this.errorMessage, { + description: error.description, + }); + return false; + }, + ); + } + + async updateProfile(userData: { + name: string; + username: string; + }): Promise { + this.loading = true; + this.errorMessage = null; + + const result = await ResultAsync.fromPromise( + authClient.updateUser({ + displayUsername: userData.username, + username: userData.username, + name: userData.name, + }), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to update profile", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ) + .andThen((response) => { + if (response.error) { + return errAsync({ + code: "API_ERROR", + message: + response.error.message ?? "Failed to update profile", + description: + response.error.statusText ?? + "Please try again later", + detail: response.error.statusText ?? "Unknown error", + }); + } + return okAsync(response.data); + }); + + const user = result.match( + (data) => { + toast.success("Profile updated successfully"); + window.location.reload(); + return (data as any)?.user as User | null; + }, + (error) => { + this.errorMessage = error.message ?? "Failed to update profile"; + toast.error(this.errorMessage, { + description: error.description, + }); + return null; + }, + ); + + this.loading = false; + return user; + } + + async changeEmail(email: string): Promise { + this.emailLoading = true; + this.errorMessage = null; + + const result = await ResultAsync.fromPromise( + authClient.changeEmail({ newEmail: email }), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to change email", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ) + .andThen((response) => { + if (response.error) { + return errAsync({ + code: "API_ERROR", + message: + response.error.message ?? "Failed to change email", + description: + response.error.statusText ?? + "Please try again later", + detail: response.error.statusText ?? "Unknown error", + }); + } + return okAsync(response.data); + }); + + const success = result.match( + () => { + toast.success("Verification email sent!", { + description: + "Please check your inbox and verify your new email address.", + }); + return true; + }, + (error) => { + this.errorMessage = error.message ?? "Failed to change email"; + toast.error(this.errorMessage, { + description: error.description, + }); + return false; + }, + ); + + this.emailLoading = false; + return success; + } +} + +export const accountVM = new AccountViewModel(); diff --git a/apps/main/src/lib/domains/account/sessions/sessions-card.svelte b/apps/main/src/lib/domains/account/sessions/sessions-card.svelte new file mode 100644 index 0000000..36c64a1 --- /dev/null +++ b/apps/main/src/lib/domains/account/sessions/sessions-card.svelte @@ -0,0 +1,182 @@ + + + + +
    + + Active Sessions +
    + + Manage and monitor your active login sessions across devices. + +
    + +
    + {#if sessionsVM.isLoading && sessionsVM.activeSessions.length === 0} +
    + +
    + {:else if sessionsVM.activeSessions.length > 0} +
    + {#each sessionsVM.activeSessions as session} + {@const { os, browser } = extractInfoFromUA( + session.userAgent ?? "", + )} +
    +
    +
    + +
    +
    +
    +

    + {browser || "Unknown Browser"} + on + {os || "Unknown OS"} +

    + {#if session.isCurrent} + + Current Session + + {/if} +
    +
    + {session.ipAddress || "Unknown IP"} +
    +
    + + + Created{" "} + {sessionsVM.formatRelativeTime( + session.createdAt.getTime(), + )} + +
    +
    +
    + {#if !session.isCurrent} + + {/if} +
    + {/each} +
    + + {#if sessionsVM.activeSessions.filter((s) => !s.isCurrent).length > 0} + + {/if} + {:else} +
    +

    + No active sessions found. This is unusual and might + indicate a problem. +

    +
    + {/if} +
    +
    +
    diff --git a/apps/main/src/lib/domains/account/sessions/sessions.vm.svelte.ts b/apps/main/src/lib/domains/account/sessions/sessions.vm.svelte.ts new file mode 100644 index 0000000..d6553cf --- /dev/null +++ b/apps/main/src/lib/domains/account/sessions/sessions.vm.svelte.ts @@ -0,0 +1,209 @@ +import type { ModifiedSession } from "@pkg/logic/domains/user/data"; +import { authClient } from "$lib/auth.client"; +import { toast } from "svelte-sonner"; +import { ResultAsync, errAsync, okAsync } from "neverthrow"; +import type { Err } from "@pkg/result"; + +class SessionsViewModel { + session: ModifiedSession | undefined = $state(undefined); + activeSessions = $state([]); + isLoading = $state(false); + errorMessage = $state(null); + + async setCurrentSession(s: ModifiedSession) { + this.session = s; + } + + async fetchActiveSessions() { + this.isLoading = true; + this.errorMessage = null; + + const result = await ResultAsync.fromPromise( + authClient.listSessions(), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to fetch active sessions", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ) + .andThen((response) => { + if (response.error) { + return errAsync({ + code: "API_ERROR", + message: "Failed to fetch active sessions", + description: response.error.message ?? "Unknown error", + detail: response.error.statusText ?? "Unknown error", + }); + } + return okAsync(response.data ?? []); + }); + + const sessions = result.match( + (data) => { + this.activeSessions = data.map((session: ModifiedSession) => ({ + ...session, + isCurrent: session.id === this.session?.id, + })); + return this.activeSessions; + }, + (error) => { + this.errorMessage = error.message ?? "Failed to fetch active sessions"; + toast.error("Failed to fetch active sessions", { + description: error.description, + }); + return []; + }, + ); + + this.isLoading = false; + return sessions; + } + + async terminateSession(sessionId: string) { + this.isLoading = true; + this.errorMessage = null; + + const result = await ResultAsync.fromPromise( + authClient.revokeSession({ + token: sessionId, + }), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to terminate session", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ) + .andThen((response) => { + if (response.error) { + return errAsync({ + code: "API_ERROR", + message: "Failed to terminate session", + description: response.error.message ?? "Unknown error", + detail: response.error.statusText ?? "Unknown error", + }); + } + return okAsync(response.data); + }); + + result.match( + () => { + this.activeSessions = this.activeSessions.filter( + (session) => session.id !== sessionId, + ); + toast.success("Session terminated"); + }, + (error) => { + this.errorMessage = error.message ?? "Failed to terminate session"; + toast.error("Failed to terminate session", { + description: error.description, + }); + }, + ); + + this.isLoading = false; + } + + async terminateAllOtherSessions() { + this.isLoading = true; + this.errorMessage = null; + + const result = await ResultAsync.fromPromise( + authClient.revokeOtherSessions(), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to terminate other sessions", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ) + .andThen((response) => { + if (response.error) { + return errAsync({ + code: "API_ERROR", + message: "Failed to terminate other sessions", + description: response.error.message ?? "Unknown error", + detail: response.error.statusText ?? "Unknown error", + }); + } + return okAsync(response.data); + }); + + result.match( + () => { + this.activeSessions = this.activeSessions.filter( + // @ts-ignore + (session) => session.isCurrent, + ); + toast.success("All other sessions terminated"); + }, + (error) => { + this.errorMessage = + error.message ?? "Failed to terminate other sessions"; + toast.error("Failed to terminate other sessions", { + description: error.description, + }); + }, + ); + + this.isLoading = false; + } + + async logout(skipToast = false) { + const result = await ResultAsync.fromPromise( + authClient.signOut(), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to log out", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ); + + result.match( + () => { + if (!skipToast) { + toast("Logged out successfully, redirecting..."); + } + setTimeout(() => { + window.location.href = "/auth/login"; + }, 500); + }, + (error) => { + toast.error("Failed to log out", { + description: error.description, + }); + }, + ); + } + + formatRelativeTime(timestamp: string | number): string { + const date = new Date(timestamp); + const now = new Date(); + const diffInSeconds = Math.floor( + (now.getTime() - date.getTime()) / 1000, + ); + + if (diffInSeconds < 60) { + return "just now"; + } else if (diffInSeconds < 3600) { + const minutes = Math.floor(diffInSeconds / 60); + return `${minutes} minute${minutes > 1 ? "s" : ""} ago`; + } else if (diffInSeconds < 86400) { + const hours = Math.floor(diffInSeconds / 3600); + return `${hours} hour${hours > 1 ? "s" : ""} ago`; + } else { + const days = Math.floor(diffInSeconds / 86400); + return `${days} day${days > 1 ? "s" : ""} ago`; + } + } + + reset() { + this.activeSessions = []; + this.isLoading = false; + this.errorMessage = null; + } +} + +export const sessionsVM = new SessionsViewModel(); diff --git a/apps/main/src/lib/domains/notifications/notification.vm.svelte.ts b/apps/main/src/lib/domains/notifications/notification.vm.svelte.ts new file mode 100644 index 0000000..0ad8ca9 --- /dev/null +++ b/apps/main/src/lib/domains/notifications/notification.vm.svelte.ts @@ -0,0 +1,536 @@ +import type { + ClientNotificationFilters, + ClientPaginationState, + Notifications, +} from "@pkg/logic/domains/notifications/data"; +import { apiClient, user } from "$lib/global.stores"; +import { toast } from "svelte-sonner"; +import { get } from "svelte/store"; +import { ResultAsync, errAsync, okAsync } from "neverthrow"; +import type { Err } from "@pkg/result"; + +class NotificationViewModel { + notifications = $state([] as Notifications); + loading = $state(false); + selectedIds = $state(new Set()); + + // Pagination state + pagination = $state({ + page: 1, + pageSize: 20, + total: 0, + totalPages: 0, + sortBy: "createdAt", + sortOrder: "desc", + }); + + // Filter state + filters = $state({ + userId: get(user)?.id!, + isArchived: false, // Default to showing non-archived + }); + + // Stats + unreadCount = $state(0); + + async fetchNotifications() { + this.loading = true; + + const params = new URLSearchParams(); + + // Add pagination params + params.append("page", this.pagination.page.toString()); + params.append("pageSize", this.pagination.pageSize.toString()); + params.append("sortBy", this.pagination.sortBy); + params.append("sortOrder", this.pagination.sortOrder); + + // Add filter params + if (this.filters.isRead !== undefined) { + params.append("isRead", this.filters.isRead.toString()); + } + if (this.filters.isArchived !== undefined) { + params.append("isArchived", this.filters.isArchived.toString()); + } + if (this.filters.type) { + params.append("type", this.filters.type); + } + if (this.filters.category) { + params.append("category", this.filters.category); + } + if (this.filters.priority) { + params.append("priority", this.filters.priority); + } + if (this.filters.search) { + params.append("search", this.filters.search); + } + + const result = await ResultAsync.fromPromise( + get(apiClient).notifications.$get({ + query: Object.fromEntries(params.entries()), + }), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to fetch notifications", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ) + .andThen((response) => { + if (!response.ok) { + return errAsync({ + code: "API_ERROR", + message: "Failed to fetch notifications", + description: `Response failed with status ${response.status}`, + detail: `HTTP ${response.status}`, + }); + } + return ResultAsync.fromPromise( + response.json(), + (error): Err => ({ + code: "PARSING_ERROR", + message: "Failed to parse response", + description: "Invalid response format", + detail: error instanceof Error ? error.message : String(error), + }), + ); + }) + .andThen((apiResult: any) => { + if (apiResult.error || !apiResult.data) { + return errAsync( + apiResult.error || { + code: "API_ERROR", + message: "Failed to fetch notifications", + description: "Invalid response data", + detail: "Missing data in response", + }, + ); + } + return okAsync(apiResult.data); + }); + + result.match( + (data) => { + this.notifications = data.data as any; + this.pagination.total = data.total; + this.pagination.totalPages = data.totalPages; + }, + (error) => { + const errorMessage = error.message || "Failed to fetch notifications"; + toast.error(errorMessage, { + description: error.description || "Please try again later", + }); + }, + ); + + this.loading = false; + } + + async markAsRead(notificationIds: number[]) { + const result = await ResultAsync.fromPromise( + get(apiClient).notifications["mark-read"].$put({ + json: { notificationIds }, + }), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to mark as read", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ) + .andThen((response) => { + if (!response.ok) { + return errAsync({ + code: "API_ERROR", + message: "Failed to mark as read", + description: `Response failed with status ${response.status}`, + detail: `HTTP ${response.status}`, + }); + } + return ResultAsync.fromPromise( + response.json(), + (error): Err => ({ + code: "PARSING_ERROR", + message: "Failed to parse response", + description: "Invalid response format", + detail: error instanceof Error ? error.message : String(error), + }), + ); + }) + .andThen((apiResult: any) => { + if (apiResult.error) { + return errAsync(apiResult.error); + } + return okAsync(apiResult.data); + }); + + result.match( + async () => { + await this.fetchNotifications(); + await this.fetchUnreadCount(); + }, + (error) => { + const errorMessage = error.message || "Failed to mark as read"; + toast.error(errorMessage, { + description: error.description || "Please try again later", + }); + }, + ); + } + + async markAsUnread(notificationIds: number[]) { + const result = await ResultAsync.fromPromise( + get(apiClient).notifications["mark-unread"].$put({ + json: { notificationIds }, + }), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to mark as unread", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ) + .andThen((response) => { + if (!response.ok) { + return errAsync({ + code: "API_ERROR", + message: "Failed to mark as unread", + description: `Response failed with status ${response.status}`, + detail: `HTTP ${response.status}`, + }); + } + return ResultAsync.fromPromise( + response.json(), + (error): Err => ({ + code: "PARSING_ERROR", + message: "Failed to parse response", + description: "Invalid response format", + detail: error instanceof Error ? error.message : String(error), + }), + ); + }) + .andThen((apiResult: any) => { + if (apiResult.error) { + return errAsync(apiResult.error); + } + return okAsync(apiResult.data); + }); + + result.match( + async () => { + await this.fetchNotifications(); + await this.fetchUnreadCount(); + }, + (error) => { + const errorMessage = error.message || "Failed to mark as unread"; + toast.error(errorMessage, { + description: error.description || "Please try again later", + }); + }, + ); + } + + async archive(notificationIds: number[]) { + const result = await ResultAsync.fromPromise( + get(apiClient).notifications.archive.$put({ + json: { notificationIds }, + }), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to archive", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ) + .andThen((response) => { + if (!response.ok) { + return errAsync({ + code: "API_ERROR", + message: "Failed to archive", + description: `Response failed with status ${response.status}`, + detail: `HTTP ${response.status}`, + }); + } + return ResultAsync.fromPromise( + response.json(), + (error): Err => ({ + code: "PARSING_ERROR", + message: "Failed to parse response", + description: "Invalid response format", + detail: error instanceof Error ? error.message : String(error), + }), + ); + }) + .andThen((apiResult: any) => { + if (apiResult.error) { + return errAsync(apiResult.error); + } + return okAsync(apiResult.data); + }); + + result.match( + async () => { + await this.fetchNotifications(); + }, + (error) => { + const errorMessage = error.message || "Failed to archive"; + toast.error(errorMessage, { + description: error.description || "Please try again later", + }); + }, + ); + } + + async unarchive(notificationIds: number[]) { + const result = await ResultAsync.fromPromise( + get(apiClient).notifications.unarchive.$put({ + json: { notificationIds }, + }), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to unarchive", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ) + .andThen((response) => { + if (!response.ok) { + return errAsync({ + code: "API_ERROR", + message: "Failed to unarchive", + description: `Response failed with status ${response.status}`, + detail: `HTTP ${response.status}`, + }); + } + return ResultAsync.fromPromise( + response.json(), + (error): Err => ({ + code: "PARSING_ERROR", + message: "Failed to parse response", + description: "Invalid response format", + detail: error instanceof Error ? error.message : String(error), + }), + ); + }) + .andThen((apiResult: any) => { + if (apiResult.error) { + return errAsync(apiResult.error); + } + return okAsync(apiResult.data); + }); + + result.match( + async () => { + await this.fetchNotifications(); + }, + (error) => { + const errorMessage = error.message || "Failed to unarchive"; + toast.error(errorMessage, { + description: error.description || "Please try again later", + }); + }, + ); + } + + async deleteNotifications(notificationIds: number[]) { + const result = await ResultAsync.fromPromise( + get(apiClient).notifications.delete.$delete({ + json: { notificationIds }, + }), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to delete", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ) + .andThen((response) => { + if (!response.ok) { + return errAsync({ + code: "API_ERROR", + message: "Failed to delete", + description: `Response failed with status ${response.status}`, + detail: `HTTP ${response.status}`, + }); + } + return ResultAsync.fromPromise( + response.json(), + (error): Err => ({ + code: "PARSING_ERROR", + message: "Failed to parse response", + description: "Invalid response format", + detail: error instanceof Error ? error.message : String(error), + }), + ); + }) + .andThen((apiResult: any) => { + if (apiResult.error) { + return errAsync(apiResult.error); + } + return okAsync(apiResult.data); + }); + + result.match( + async () => { + await this.fetchNotifications(); + await this.fetchUnreadCount(); + }, + (error) => { + const errorMessage = error.message || "Failed to delete"; + toast.error(errorMessage, { + description: error.description || "Please try again later", + }); + }, + ); + } + + async markAllAsRead() { + const result = await ResultAsync.fromPromise( + get(apiClient).notifications["mark-all-read"].$put(), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to mark all as read", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ) + .andThen((response) => { + if (!response.ok) { + return errAsync({ + code: "API_ERROR", + message: "Failed to mark all as read", + description: `Response failed with status ${response.status}`, + detail: `HTTP ${response.status}`, + }); + } + return ResultAsync.fromPromise( + response.json(), + (error): Err => ({ + code: "PARSING_ERROR", + message: "Failed to parse response", + description: "Invalid response format", + detail: error instanceof Error ? error.message : String(error), + }), + ); + }) + .andThen((apiResult: any) => { + if (apiResult.error) { + return errAsync(apiResult.error); + } + return okAsync(apiResult.data); + }); + + result.match( + async () => { + await this.fetchNotifications(); + await this.fetchUnreadCount(); + }, + (error) => { + const errorMessage = error.message || "Failed to mark all as read"; + toast.error(errorMessage, { + description: error.description || "Please try again later", + }); + }, + ); + } + + async fetchUnreadCount() { + const result = await ResultAsync.fromPromise( + get(apiClient).notifications["unread-count"].$get(), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to fetch unread count", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ) + .andThen((response) => { + if (!response.ok) { + return errAsync({ + code: "API_ERROR", + message: "Failed to fetch unread count", + description: `Response failed with status ${response.status}`, + detail: `HTTP ${response.status}`, + }); + } + return ResultAsync.fromPromise( + response.json(), + (error): Err => ({ + code: "PARSING_ERROR", + message: "Failed to parse response", + description: "Invalid response format", + detail: error instanceof Error ? error.message : String(error), + }), + ); + }) + .andThen((apiResult: any) => { + if (apiResult.error) { + return errAsync(apiResult.error); + } + return okAsync(apiResult.data); + }); + + result.match( + (data) => { + if (data !== undefined && data !== null) { + this.unreadCount = data as number; + } + }, + (error) => { + // Silently fail for unread count - don't show toast as it's not critical + console.error("Failed to fetch unread count:", error); + }, + ); + } + + // Helper methods + toggleSelection(id: number) { + if (this.selectedIds.has(id)) { + this.selectedIds.delete(id); + } else { + this.selectedIds.add(id); + } + } + + selectAll() { + this.notifications.forEach((n) => this.selectedIds.add(n.id)); + } + + clearSelection() { + this.selectedIds.clear(); + } + + goToPage(page: number) { + this.pagination.page = page; + this.fetchNotifications(); + } + + setPageSize(pageSize: number) { + this.pagination.pageSize = pageSize; + this.pagination.page = 1; // Reset to first page + this.fetchNotifications(); + } + + setSorting( + sortBy: ClientPaginationState["sortBy"], + sortOrder: ClientPaginationState["sortOrder"], + ) { + this.pagination.sortBy = sortBy; + this.pagination.sortOrder = sortOrder; + this.pagination.page = 1; // Reset to first page + this.fetchNotifications(); + } + + setFilters(newFilters: Partial) { + this.filters = { ...this.filters, ...newFilters }; + this.pagination.page = 1; // Reset to first page + this.fetchNotifications(); + } + + clearFilters() { + this.filters = { userId: get(user)?.id!, isArchived: false }; + this.pagination.page = 1; + this.fetchNotifications(); + } +} + +export const notificationViewModel = new NotificationViewModel(); diff --git a/apps/main/src/lib/domains/notifications/notifications-table.svelte b/apps/main/src/lib/domains/notifications/notifications-table.svelte new file mode 100644 index 0000000..cc263a1 --- /dev/null +++ b/apps/main/src/lib/domains/notifications/notifications-table.svelte @@ -0,0 +1,566 @@ + + + + +
    +
    + + Notifications + {#if notificationViewModel.unreadCount > 0} + + {notificationViewModel.unreadCount} unread + + {/if} +
    + + {#if hasSelection} +
    + + {notificationViewModel.selectedIds.size} selected + + + + +
    + {:else} + + {/if} +
    + +
    + +
    + + +
    + + +
    + + + + Filters + + + + handleFilterChange("isRead", undefined)} + > + All + + handleFilterChange("isRead", false)} + > + Unread Only + + handleFilterChange("isRead", true)} + > + Read Only + + + + handleFilterChange("isArchived", false)} + > + Active + + handleFilterChange("isArchived", true)} + > + Archived + + + +
    +
    +
    + + + {#if notificationViewModel.loading && notificationViewModel.notifications.length === 0} +
    + +
    + {:else if notificationViewModel.notifications.length === 0} +
    + +

    No notifications

    +

    + {notificationViewModel.filters.search + ? "No notifications match your search." + : "You're all caught up!"} +

    +
    + {:else} +
    + + + + + + + + + Notification + Priority + Time + + + + + {#each notificationViewModel.notifications as notification (notification.id)} + + + + handleRowSelect( + notification.id, + checked, + )} + /> + + +
    + +
    +
    + +
    +

    + {notification.title} +

    +

    + {notification.body} +

    + {#if notification.category} + + {notification.category} + + {/if} +
    +
    + + + {notification.priority} + + + +
    + + {formatRelativeTime( + notification.sentAt, + )} +
    +
    + + + + + + + {#if notification.isRead} + + handleSingleAction( + notification.id, + "mark-unread", + )} + > + Mark as Unread + + {:else} + + handleSingleAction( + notification.id, + "mark-read", + )} + > + Mark as Read + + {/if} + + handleSingleAction( + notification.id, + "archive", + )} + > + Archive + + + + handleSingleAction( + notification.id, + "delete", + )} + class="text-destructive" + > + Delete + + + + +
    + {/each} +
    +
    + + + {#if notificationViewModel.pagination.totalPages > 1} +
    +
    + Showing {(notificationViewModel.pagination.page - 1) * + notificationViewModel.pagination.pageSize + + 1} to {Math.min( + notificationViewModel.pagination.page * + notificationViewModel.pagination.pageSize, + notificationViewModel.pagination.total, + )} of {notificationViewModel.pagination.total} notifications +
    + +
    + + +
    + {#each Array.from( { length: Math.min(5, notificationViewModel.pagination.totalPages) }, (_, i) => { + const startPage = Math.max(1, notificationViewModel.pagination.page - 2); + return startPage + i; + }, ) as page} + {#if page <= notificationViewModel.pagination.totalPages} + + {/if} + {/each} +
    + + +
    +
    + {/if} +
    + {/if} +
    +
    + + diff --git a/apps/main/src/lib/domains/security/auth.vm.svelte.ts b/apps/main/src/lib/domains/security/auth.vm.svelte.ts new file mode 100644 index 0000000..1ef7e88 --- /dev/null +++ b/apps/main/src/lib/domains/security/auth.vm.svelte.ts @@ -0,0 +1,233 @@ +import { apiClient } from "$lib/global.stores"; +import { authClient } from "$lib/auth.client"; +import { toast } from "svelte-sonner"; +import { get } from "svelte/store"; +import { page } from "$app/state"; +import { ResultAsync, errAsync, okAsync } from "neverthrow"; +import type { Err } from "@pkg/result"; + +class AuthViewModel { + loggingIn = $state(false); + loginError = $state(null); + + async handleGoogleOAuthLogin() { + const result = await ResultAsync.fromPromise( + authClient.signIn.social({ provider: "google" }), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to initiate Google OAuth login", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ); + + result.match( + () => { + // OAuth flow will redirect, no action needed + }, + (error) => { + toast.error("Failed to initiate Google login", { + description: error.description, + }); + }, + ); + } + + async loginWithEmail(data: FormData): Promise { + const email = data.get("email")?.toString(); + + if (!email || email.length < 5) { + toast.error("Please enter a valid email"); + return false; + } + + this.loggingIn = true; + this.loginError = null; + + const result = await ResultAsync.fromPromise( + authClient.signIn.magicLink({ email }), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to send magic link", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ) + .andThen((response) => { + if (response.error) { + return errAsync({ + code: "API_ERROR", + message: response.error.message ?? "Something went wrong", + description: + response.error.statusText ?? + "Please try another login method or try again later", + detail: response.error.statusText ?? "Unknown error", + }); + } + return okAsync(response.data); + }); + + const success = result.match( + () => { + this.loginError = null; + toast.success("Login request sent", { + description: "Check your email for a magic link to log in", + }); + return true; + }, + (error) => { + // Set error message for UI display + const errorMessage = error.message ?? "Something went wrong"; + this.loginError = errorMessage; + + // Don't show toast for known error cases that we handle in the UI + if ( + !errorMessage.includes("not valid to login") && + !errorMessage.includes("not allowed") + ) { + toast.error(errorMessage, { + description: error.description, + }); + } + return false; + }, + ); + + this.loggingIn = false; + return success; + } + + clearLoginError() { + this.loginError = null; + } + + async verifyMagicLink() { + const token = page.url.searchParams.get("token"); + if (!token) { + throw new Error("No token provided"); + } + + const result = await ResultAsync.fromPromise( + authClient.magicLink.verify({ query: { token } }), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to verify magic link", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ) + .andThen((response) => { + if (response.error) { + return errAsync({ + code: "API_ERROR", + message: "Could not verify magic link", + description: response.error.message ?? "Unknown error", + detail: response.error.statusText ?? "Unknown error", + }); + } + if (!response.data?.user?.id) { + return errAsync({ + code: "API_ERROR", + message: "Invalid verification result", + description: "User ID not found in verification response", + detail: "Missing user.id in response", + }); + } + return okAsync(response.data); + }) + .andThen((verificationResult) => { + return ResultAsync.fromPromise( + get(apiClient).users["ensure-account-exists"].$put({ + json: { userId: verificationResult.user.id }, + }), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to ensure account exists", + description: "Network request failed", + detail: + error instanceof Error + ? error.message + : String(error), + }), + ) + .andThen((response) => { + if (!response.ok) { + return errAsync({ + code: "API_ERROR", + message: "Failed to ensure account exists", + description: `Response failed with status ${response.status}`, + detail: `HTTP ${response.status}`, + }); + } + return ResultAsync.fromPromise( + response.json(), + (error): Err => ({ + code: "PARSING_ERROR", + message: "Failed to parse response", + description: "Invalid response format", + detail: + error instanceof Error + ? error.message + : String(error), + }), + ); + }) + .andThen((apiResult: any) => { + if (apiResult.error) { + return errAsync(apiResult.error); + } + return okAsync(apiResult.data); + }); + }); + + result.match( + () => { + // Success - account ensured + }, + (error: Err) => { + throw new Error(error.message ?? "Failed to verify magic link"); + }, + ); + } + + async verifyEmailChange() { + const token = page.url.searchParams.get("token"); + if (!token) { + throw new Error("No verification token provided"); + } + + const result = await ResultAsync.fromPromise( + authClient.verifyEmail({ + query: { token }, + }), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to verify email change", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ) + .andThen((response) => { + if (response.error) { + return errAsync({ + code: "API_ERROR", + message: "Could not verify email change", + description: response.error.message ?? "Unknown error", + detail: response.error.statusText ?? "Unknown error", + }); + } + return okAsync(response.data); + }); + + result.match( + () => { + // Success - email verified + }, + (error: Err) => { + throw new Error(error.message ?? "Could not verify email change"); + }, + ); + } +} + +export const authVM = new AuthViewModel(); diff --git a/apps/main/src/lib/domains/security/email-login-form.svelte b/apps/main/src/lib/domains/security/email-login-form.svelte new file mode 100644 index 0000000..ab67c11 --- /dev/null +++ b/apps/main/src/lib/domains/security/email-login-form.svelte @@ -0,0 +1,154 @@ + + +
    + +
    + +
    + (emailFocused = true)} + onblur={() => (emailFocused = false)} + class="h-12 pr-12" + aria-invalid={!isEmailValid && email.length > 0} + /> + +
    + {#if authVM.loggingIn} + + {:else if isEmailValid && email.length > 0} + + {:else if email.length > 0} + + + {/if} +
    +
    + + {#if emailFocused && email.length > 0 && !isEmailValid} +

    + Please enter a valid email address. +

    + {/if} +
    + + + + + +
    + {#if status === "sent"} +
    +
    + +

    + We’ve sent a magic link to {submittedEmail}. Check your inbox. +

    +
    +
    + {:else if status === "failed" && authVM.loginError} +
    +
    + +

    {authVM.loginError}

    +
    + +
    + {/if} +
    +
    diff --git a/apps/main/src/lib/domains/security/magic-link-verification.svelte b/apps/main/src/lib/domains/security/magic-link-verification.svelte new file mode 100644 index 0000000..5f58a66 --- /dev/null +++ b/apps/main/src/lib/domains/security/magic-link-verification.svelte @@ -0,0 +1,228 @@ + + + + +
    + {#if verificationState === "loading"} + +
    +
    +
    + +
    +
    + {/if} + + + {#if verificationState === "loading"} +
    + + Verifying Your Login + + +
    + + Please wait while we verify your magic + link... +
    + + +
    +
    +
    + + +
    +
    +
    + +
    + Secure encrypted connection +
    +
    +
    + +
    + Verifying your identity +
    +
    +
    + +
    + Processing authentication +
    +
    +
    + {:else if verificationState === "success"} +
    +
    +
    +
    + +
    +
    + + + Login Successful! + + +

    + You've been successfully authenticated. + Redirecting you now... +

    + +
    + Redirecting + +
    +
    + {:else if verificationState === "error"} +
    +
    +
    +
    + +
    +
    + + + Verification Failed + + +

    + {errorMessage || + "We couldn't verify your magic link. It may have expired or been used already."} +

    + +
    + + +

    + Need help? Contact our support team if this + problem persists. +

    +
    +
    + {/if} + + +
    +

    + Magic links are secure, one-time use authentication + tokens that expire after 10 minutes. +

    +
    +
    +
    +
    diff --git a/apps/main/src/lib/domains/todo/list.svelte b/apps/main/src/lib/domains/todo/list.svelte new file mode 100644 index 0000000..e69de29 diff --git a/apps/main/src/lib/global.stores.ts b/apps/main/src/lib/global.stores.ts new file mode 100644 index 0000000..88b4e38 --- /dev/null +++ b/apps/main/src/lib/global.stores.ts @@ -0,0 +1,14 @@ +import type { Session, User } from "@pkg/logic/domains/user/data"; +import type { AppSidebarItem } from "./core/constants"; +import { writable } from "svelte/store"; +import type { Router } from "$lib/api"; +import type { hc } from "hono/client"; + +export const breadcrumbs = writable>([ + { title: "Dashboard", url: "/dashboard" }, +]); + +export const apiClient = writable>>(undefined); + +export const user = writable(undefined); +export const session = writable(undefined); diff --git a/apps/main/src/lib/hooks/is-mobile.svelte.ts b/apps/main/src/lib/hooks/is-mobile.svelte.ts new file mode 100644 index 0000000..4829c00 --- /dev/null +++ b/apps/main/src/lib/hooks/is-mobile.svelte.ts @@ -0,0 +1,9 @@ +import { MediaQuery } from "svelte/reactivity"; + +const DEFAULT_MOBILE_BREAKPOINT = 768; + +export class IsMobile extends MediaQuery { + constructor(breakpoint: number = DEFAULT_MOBILE_BREAKPOINT) { + super(`max-width: ${breakpoint - 1}px`); + } +} diff --git a/apps/main/src/lib/make-client.ts b/apps/main/src/lib/make-client.ts new file mode 100644 index 0000000..999475d --- /dev/null +++ b/apps/main/src/lib/make-client.ts @@ -0,0 +1,21 @@ +import type { Router } from "$lib/api"; +import { hc } from "hono/client"; + +let browserClient: ReturnType>; + +export const makeClient = (fetch: Window["fetch"]) => { + const isBrowser = typeof window !== "undefined"; + const origin = isBrowser ? window.location.origin : ""; + + if (isBrowser && browserClient) { + return browserClient; + } + + const client = hc(origin + "/api/v1", { fetch }); + + if (isBrowser) { + browserClient = client; + } + + return client; +}; diff --git a/apps/main/src/lib/utils.ts b/apps/main/src/lib/utils.ts new file mode 100644 index 0000000..55b3a91 --- /dev/null +++ b/apps/main/src/lib/utils.ts @@ -0,0 +1,13 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChild = T extends { child?: any } ? Omit : T; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChildren = T extends { children?: any } ? Omit : T; +export type WithoutChildrenOrChild = WithoutChildren>; +export type WithElementRef = T & { ref?: U | null }; diff --git a/apps/main/src/routes/(main)/+layout.server.ts b/apps/main/src/routes/(main)/+layout.server.ts new file mode 100644 index 0000000..93ad849 --- /dev/null +++ b/apps/main/src/routes/(main)/+layout.server.ts @@ -0,0 +1,13 @@ +import { auth } from "@pkg/logic/domains/auth/config.base"; +import { redirect } from "@sveltejs/kit"; +import type { LayoutServerLoad } from "./$types"; + +export const load = (async (c) => { + const sess = await auth.api.getSession({ + headers: c.request.headers, + }); + if ((!sess?.user || !sess?.session) && c.url.pathname !== "/auth/login") { + return redirect(302, "/auth/login"); + } + return { user: c.locals.user, session: c.locals.session }; +}) satisfies LayoutServerLoad; diff --git a/apps/main/src/routes/(main)/+layout.svelte b/apps/main/src/routes/(main)/+layout.svelte new file mode 100644 index 0000000..f39d355 --- /dev/null +++ b/apps/main/src/routes/(main)/+layout.svelte @@ -0,0 +1,58 @@ + + + + + +
    +
    + + + + + {#if $breadcrumbs.length > 0} + {#each $breadcrumbs as breadcrumb, i} + + {#if i < $breadcrumbs.length - 1} + + +
    +
    +
    + {@render children()} +
    +
    +
    diff --git a/apps/main/src/routes/(main)/+page.server.ts b/apps/main/src/routes/(main)/+page.server.ts new file mode 100644 index 0000000..f2058f3 --- /dev/null +++ b/apps/main/src/routes/(main)/+page.server.ts @@ -0,0 +1,6 @@ +import { redirect } from "@sveltejs/kit"; +import type { PageServerLoad } from "./$types"; + +export const load = (async (c) => { + throw redirect(302, "/dashboard"); +}) satisfies PageServerLoad; diff --git a/apps/main/src/routes/(main)/+page.svelte b/apps/main/src/routes/(main)/+page.svelte new file mode 100644 index 0000000..93b9d02 --- /dev/null +++ b/apps/main/src/routes/(main)/+page.svelte @@ -0,0 +1,18 @@ + diff --git a/apps/main/src/routes/(main)/account/+layout.svelte b/apps/main/src/routes/(main)/account/+layout.svelte new file mode 100644 index 0000000..fb25929 --- /dev/null +++ b/apps/main/src/routes/(main)/account/+layout.svelte @@ -0,0 +1,134 @@ + + + + + + +
    + {@render children()} +
    +
    + + +{#if isMobile} + + + + + +
    + + + {#each secondaryNavTree as each} + { + handleNavigation(each.url); + }} + > + +

    + {each.title} +

    +
    + {/each} +
    +
    +
    +
    +
    +{/if} diff --git a/apps/main/src/routes/(main)/account/+page.svelte b/apps/main/src/routes/(main)/account/+page.svelte new file mode 100644 index 0000000..3031c4d --- /dev/null +++ b/apps/main/src/routes/(main)/account/+page.svelte @@ -0,0 +1,247 @@ + + +
    + + + +
    +
    +
    + + {#if user.image} + + {:else} + + {(user.name || "User") + .substring(0, 2) + .toUpperCase()} + + {/if} + + +
    +
    +

    + {user.name} +

    +

    + Member since {new Date( + user.createdAt.toString(), + ).toLocaleDateString()} +

    +
    +
    +
    + + + + Personal Information + + Update your personal information and how others see you on the + platform. + +
    + + +
    + +
    + + +
    + + +
    + + +

    + This is your public username visible to other users. +

    +
    + +
    + +
    +
    +
    + +

    + Last updated: {new Date( + user.updatedAt.toString(), + ).toLocaleString()} +

    +
    +
    + + + + + Email Settings + + Manage your email address and verification status. + + + +
    + +
    + + +
    + + {user.emailVerified + ? "Email verified" + : "Email not verified"} + + {#if !user.emailVerified} + + {/if} +
    +
    + +
    + +
    +
    +
    + +

    + Changing your email will require verification of the new + address. +

    +
    +
    +
    diff --git a/apps/main/src/routes/(main)/account/verify-email/+page.svelte b/apps/main/src/routes/(main)/account/verify-email/+page.svelte new file mode 100644 index 0000000..f128cee --- /dev/null +++ b/apps/main/src/routes/(main)/account/verify-email/+page.svelte @@ -0,0 +1,240 @@ + + +
    +
    + + +
    + {#if verificationState === "loading"} + +
    +
    +
    + +
    +
    + {/if} + + + {#if verificationState === "loading"} +
    + + Verifying Your Email + + +
    + + Please wait while we verify your email + change... +
    + + +
    +
    +
    + + +
    +
    +
    + +
    + Secure encrypted connection +
    +
    +
    + +
    + Validating email ownership +
    +
    +
    + +
    + Updating account settings +
    +
    +
    + {:else if verificationState === "success"} +
    +
    +
    +
    + +
    +
    + + + Email Verified Successfully! + + +

    + Your email address has been updated and + verified. Redirecting you to your account... +

    + +
    + Redirecting + +
    +
    + {:else if verificationState === "error"} +
    +
    +
    +
    + +
    +
    + + + Email Verification Failed + + +

    + {errorMessage || + "We couldn't verify your email change. The verification link may have expired or been used already."} +

    + +
    + + +

    + You can try changing your email again from + your account settings. +

    +
    +
    + {/if} + + +
    +

    + Email verification links are secure, one-time use + tokens that expire after 10 minutes. +

    +
    +
    +
    +
    +
    +
    diff --git a/apps/main/src/routes/(main)/dashboard/+page.svelte b/apps/main/src/routes/(main)/dashboard/+page.svelte new file mode 100644 index 0000000..797b356 --- /dev/null +++ b/apps/main/src/routes/(main)/dashboard/+page.svelte @@ -0,0 +1,18 @@ + + + +
    +

    + Dashboard Not Yet Implemented +

    +

    + This is where your implementation will go +

    +
    +
    diff --git a/apps/main/src/routes/(main)/notifications/+page.svelte b/apps/main/src/routes/(main)/notifications/+page.svelte new file mode 100644 index 0000000..1d5bd87 --- /dev/null +++ b/apps/main/src/routes/(main)/notifications/+page.svelte @@ -0,0 +1,27 @@ + + + + + diff --git a/apps/main/src/routes/(main)/users/+page.server.ts b/apps/main/src/routes/(main)/users/+page.server.ts new file mode 100644 index 0000000..cc76a46 --- /dev/null +++ b/apps/main/src/routes/(main)/users/+page.server.ts @@ -0,0 +1,13 @@ +import { UserRoleMap } from "@pkg/logic/domains/user/data"; +import type { PageServerLoad } from "./$types"; +import { redirect } from "@sveltejs/kit"; + +// Info: this ensures only admin type users can access this page + +export const load = (async (c) => { + const user = c.locals.user; + if (!user || user.role !== UserRoleMap.admin) { + return redirect(307, "/dashboard"); + } + return {}; +}) satisfies PageServerLoad; diff --git a/apps/main/src/routes/(main)/users/+page.svelte b/apps/main/src/routes/(main)/users/+page.svelte new file mode 100644 index 0000000..2fb8dad --- /dev/null +++ b/apps/main/src/routes/(main)/users/+page.svelte @@ -0,0 +1,73 @@ + + + + + +
    + Users +
    + + +
    +
    + +
    + +
    + + { + toast.info("Searching...", { + description: "simulating API call", + }); + }} + /> +
    +
    + +
    + INFO: Normally would show all the users table here + +
    +
    +
    +
    diff --git a/apps/main/src/routes/+layout.svelte b/apps/main/src/routes/+layout.svelte new file mode 100644 index 0000000..345a779 --- /dev/null +++ b/apps/main/src/routes/+layout.svelte @@ -0,0 +1,36 @@ + + + + {$breadcrumbs[$breadcrumbs.length - 1]?.title ?? "Dashboard"} + + + + + + + + + {@render children()} + diff --git a/apps/main/src/routes/api/debug/users/+server.ts b/apps/main/src/routes/api/debug/users/+server.ts new file mode 100644 index 0000000..725c0de --- /dev/null +++ b/apps/main/src/routes/api/debug/users/+server.ts @@ -0,0 +1,92 @@ +import { UserRoleMap } from "@pkg/logic/domains/user/data"; +import { user } from "@pkg/db/schema/better.auth.schema"; +import type { RequestHandler } from "./$types"; +import { env } from "$env/dynamic/private"; +import { logger } from "@pkg/logger"; +import { db, eq } from "@pkg/db"; +import { nanoid } from "nanoid"; +import * as v from "valibot"; + +function isAuthorized(authHeader?: string | null) { + if (!authHeader) return false; + const authToken = authHeader.toString().replace("Bearer ", ""); + return authToken === env.DEBUG_API_KEY; +} + +export const GET: RequestHandler = async ({ request }) => { + if (!isAuthorized(request.headers.get("Authorization"))) { + return new Response("Unauthorized", { status: 401 }); + } + const users = await db.query.user.findMany({}); + return new Response(JSON.stringify({ users }), { status: 200 }); +}; + +export const PUT: RequestHandler = async ({ request }) => { + if (!isAuthorized(request.headers.get("Authorization"))) { + return new Response("Unauthorized", { status: 401 }); + } + + const data = await request.json(); + + if (!data.username) { + return new Response("Invalid data", { status: 400 }); + } + + await db + .update(user) + .set({ role: UserRoleMap.admin }) + .where(eq(user.username, data.username)) + .execute(); + + return new Response("Not implemented", { status: 200 }); +}; + +export const POST: RequestHandler = async ({ request }) => { + if (!isAuthorized(request.headers.get("Authorization"))) { + return new Response("Unauthorized", { status: 401 }); + } + const data = await request.json(); + const _schema = v.object({ + username: v.string(), + email: v.string(), + usertype: v.enum(UserRoleMap), + }); + const res = v.safeParse(_schema, data); + if (!res.success) { + return new Response("Invalid data", { status: 400 }); + } + + if ( + !!( + await db.query.user + .findFirst({ + where: eq(user.role, UserRoleMap.user), + columns: { id: true }, + }) + .execute() + )?.id + ) { + return new Response("Admin already exists", { status: 400 }); + } + + const resData = res.output; + + logger.info( + `Creating ${resData.username} | ${resData.email} (${resData.usertype})`, + ); + + const out = await db.insert(user).values({ + id: nanoid(), + username: resData.username, + email: resData.email, + emailVerified: false, + name: resData.username, + role: resData.usertype, + createdAt: new Date(), + updatedAt: new Date(), + }); + + logger.debug(out); + + return new Response(JSON.stringify({ ...out }), { status: 200 }); +}; diff --git a/apps/main/src/routes/api/v1/[...paths]/+server.ts b/apps/main/src/routes/api/v1/[...paths]/+server.ts new file mode 100644 index 0000000..3613780 --- /dev/null +++ b/apps/main/src/routes/api/v1/[...paths]/+server.ts @@ -0,0 +1,93 @@ +import { getUserController } from "@pkg/logic/domains/user/controller"; +import type { Session, User } from "@pkg/logic/domains/user/data"; +import { auth } from "@pkg/logic/domains/auth/config.base"; +import type { RequestHandler } from "@sveltejs/kit"; +import { env } from "$env/dynamic/private"; +import { api } from "$lib/api"; + +async function createContext(locals: App.Locals) { + return { ...env, locals }; +} + +async function getExecutionContext(sess: Session, user: User) { + const flowId = crypto.randomUUID(); + return { + flowId: flowId, + userId: user.id, + sessionId: sess.id, + }; +} + +async function getContext(headers: Headers) { + const sess = await auth.api.getSession({ headers }); + if (!sess?.session) { + return false; + } + + // @ts-ignore + const fCtx = getExecutionContext(sess.session, sess.user); + + return await getUserController() + .getUserInfo(fCtx, sess.user.id) + .match( + (user) => { + return { + user: user, + session: sess.session, + fCtx: fCtx, + }; + }, + (error) => { + console.error(error); + return false; + }, + ); +} + +export const GET: RequestHandler = async ({ request }) => { + const context = await getContext(request.headers); + if (!context || typeof context === "boolean") { + return new Response("Unauthorized", { status: 401 }); + } + return api.fetch(request, await createContext(context)); +}; + +export const HEAD: RequestHandler = async ({ request }) => { + const context = await getContext(request.headers); + if (!context || typeof context === "boolean") { + return new Response("Unauthorized", { status: 401 }); + } + return api.fetch(request, await createContext(context)); +}; + +export const POST: RequestHandler = async ({ request }) => { + const context = await getContext(request.headers); + if (!context || typeof context === "boolean") { + return new Response("Unauthorized", { status: 401 }); + } + return api.fetch(request, await createContext(context)); +}; + +export const PUT: RequestHandler = async ({ request }) => { + const context = await getContext(request.headers); + if (!context || typeof context === "boolean") { + return new Response("Unauthorized", { status: 401 }); + } + return api.fetch(request, await createContext(context)); +}; + +export const DELETE: RequestHandler = async ({ request }) => { + const context = await getContext(request.headers); + if (!context || typeof context === "boolean") { + return new Response("Unauthorized", { status: 401 }); + } + return api.fetch(request, await createContext(context)); +}; + +export const OPTIONS: RequestHandler = async ({ request }) => { + const context = await getContext(request.headers); + if (!context || typeof context === "boolean") { + return new Response("Unauthorized", { status: 401 }); + } + return api.fetch(request, await createContext(context)); +}; diff --git a/apps/main/src/routes/auth/2fa/+layout.server.ts b/apps/main/src/routes/auth/2fa/+layout.server.ts new file mode 100644 index 0000000..e01db46 --- /dev/null +++ b/apps/main/src/routes/auth/2fa/+layout.server.ts @@ -0,0 +1,14 @@ +import { auth } from "@pkg/logic/domains/auth/config.base"; +import { redirect } from "@sveltejs/kit"; +import type { LayoutServerLoad } from "./$types"; + +export const load = (async ({ request }) => { + const sess = await auth.api.getSession({ + headers: request.headers, + }); + if (!sess || !sess.user || !sess.session) { + return redirect(302, "/auth/login"); + } + // sess.session.id = + return { user: sess.user as any, session: sess.session as any }; +}) satisfies LayoutServerLoad; diff --git a/apps/main/src/routes/auth/2fa/+page.svelte b/apps/main/src/routes/auth/2fa/+page.svelte new file mode 100644 index 0000000..18d6259 --- /dev/null +++ b/apps/main/src/routes/auth/2fa/+page.svelte @@ -0,0 +1,285 @@ + + +
    +
    + + {#if !mounted || twoFactorVerifyVM.startingVerification} + + +
    +
    +
    +
    + +
    +
    + +
    + + Preparing Verification + + +
    + + Setting up secure verification... +
    + + +
    +
    +
    +
    +
    +
    + {:else if twoFactorVerifyVM.verificationToken} + + +
    + + + + Two-Factor Authentication + + +
    + + Enter the 6-digit code from your authenticator app to + continue + +
    + + + +
    +
    + + {#snippet children({ cells })} + + {#each cells.slice(0, 3) as cell (cell)} + + {/each} + + + + {#each cells.slice(3, 6) as cell (cell)} + + {/each} + + {/snippet} + +
    + + {#if twoFactorVerifyVM.errorMessage} +
    + + {twoFactorVerifyVM.errorMessage} +
    + {/if} + + +
    + + +
    +
    + +

    OR

    + +
    + + +
    + + +
    +
    +

    + Having trouble? +

    +
    + + +
    +
    +
    + + +
    +
    + + + This verification expires in 10 minutes for your + security + +
    +
    +
    + {:else} + + +
    +
    +
    +
    + +
    +
    + +
    + + Verification Setup Failed + + +

    + {twoFactorVerifyVM.errorMessage || + "We couldn't set up your verification session. Please try again."} +

    + +
    + + + +
    +
    +
    +
    + {/if} +
    +
    +
    diff --git a/apps/main/src/routes/auth/login/+page.server.ts b/apps/main/src/routes/auth/login/+page.server.ts new file mode 100644 index 0000000..cee74fb --- /dev/null +++ b/apps/main/src/routes/auth/login/+page.server.ts @@ -0,0 +1,13 @@ +import { redirect } from "@sveltejs/kit"; +import type { PageServerLoad } from "./$types"; +import { auth } from "@pkg/logic/domains/auth/config.base"; + +export const load = (async (c) => { + const sess = await auth.api.getSession({ + headers: c.request.headers, + }); + + if (!!sess && !!sess.user && !!sess.session) { + return redirect(302, "/"); + } +}) satisfies PageServerLoad; diff --git a/apps/main/src/routes/auth/login/+page.svelte b/apps/main/src/routes/auth/login/+page.svelte new file mode 100644 index 0000000..e4c5e5c --- /dev/null +++ b/apps/main/src/routes/auth/login/+page.svelte @@ -0,0 +1,77 @@ + + +
    + +
    +
    +
    +
    +
    + +
    + + +
    +
    +
    +
    + +
    +
    + +
    + + + Welcome Back + + + + Sign in to your account to continue + +
    +
    +
    + + + + +
    +
    +

    OR

    +
    +
    +
    + + +

    + By signing in, you agree to our + + and + +

    +
    +
    +
    +
    diff --git a/apps/main/src/routes/auth/magic-link/+page.svelte b/apps/main/src/routes/auth/magic-link/+page.svelte new file mode 100644 index 0000000..57a39b0 --- /dev/null +++ b/apps/main/src/routes/auth/magic-link/+page.svelte @@ -0,0 +1,11 @@ + + +
    +
    + +
    +
    diff --git a/apps/main/src/routes/layout.css b/apps/main/src/routes/layout.css new file mode 100644 index 0000000..5860334 --- /dev/null +++ b/apps/main/src/routes/layout.css @@ -0,0 +1,196 @@ +@import "tailwindcss"; +@import "tw-animate-css"; +@plugin "@tailwindcss/forms"; +@plugin "@tailwindcss/typography"; + +@font-face { + font-family: "Manrope"; + src: url("/fonts/manrope-variable.ttf") format("truetype"); +} + +@custom-variant dark (&:is(.dark *)); + +:root { + --background: oklch(0.994 0 0); + --foreground: oklch(0 0 0); + + --card: oklch(0.994 0 0); + --card-foreground: oklch(0 0 0); + + --popover: oklch(0.991 0 0); + --popover-foreground: oklch(0 0 0); + + /* --- main theme: lavender/royal purple --- */ + --primary: oklch(0.6 0.2 280); /* medium lavender purple */ + --primary-foreground: oklch(0.99 0 0); + + --secondary: oklch(0.93 0.05 285); /* soft pale lavender */ + --secondary-foreground: oklch(0.25 0.03 285); + + --muted: oklch(0.96 0.01 275); + --muted-foreground: oklch(0.4 0.01 278); + + --accent: oklch(0.86 0.08 275); /* lavender accent */ + --accent-foreground: oklch(0.5 0.15 280); + + --destructive: oklch(0.63 0.18 25); + --destructive-foreground: oklch(1 0 0); + + --border: oklch(0.92 0.02 284); + --input: oklch(0.94 0 0); + --ring: oklch(0.6 0.2 280); + + /* charts — more variety but still within lavender spectrum */ + --chart-1: oklch(0.7 0.16 275); + --chart-2: oklch(0.6 0.2 280); + --chart-3: oklch(0.72 0.18 295); /* slightly more magenta */ + --chart-4: oklch(0.65 0.15 265); /* slightly bluer lavender */ + --chart-5: oklch(0.76 0.1 285); + + --sidebar: oklch(0.97 0.01 280); + --sidebar-foreground: oklch(0 0 0); + --sidebar-primary: oklch(0.6 0.2 280); + --sidebar-primary-foreground: oklch(1 0 0); + --sidebar-accent: oklch(0.92 0.02 284); + --sidebar-accent-foreground: oklch(0.2 0.02 280); + --sidebar-border: oklch(0.92 0.02 284); + --sidebar-ring: oklch(0.6 0.2 280); + + --font-sans: Plus Jakarta Sans, sans-serif; + --font-serif: Lora, serif; + --font-mono: IBM Plex Mono, monospace; + + --radius: 0.69rem; + + --shadow-2xs: 0px 2px 3px 0px hsl(0 0% 0% / 0.08); + --shadow-xs: 0px 2px 3px 0px hsl(0 0% 0% / 0.08); + --shadow-sm: + 0px 2px 3px 0px hsl(0 0% 0% / 0.16), + 0px 1px 2px -1px hsl(0 0% 0% / 0.16); + --shadow: + 0px 2px 3px 0px hsl(0 0% 0% / 0.16), + 0px 1px 2px -1px hsl(0 0% 0% / 0.16); + --shadow-md: + 0px 2px 3px 0px hsl(0 0% 0% / 0.16), + 0px 2px 4px -1px hsl(0 0% 0% / 0.16); + --shadow-lg: + 0px 2px 3px 0px hsl(0 0% 0% / 0.16), + 0px 4px 6px -1px hsl(0 0% 0% / 0.16); + --shadow-xl: + 0px 2px 3px 0px hsl(0 0% 0% / 0.16), + 0px 8px 10px -1px hsl(0 0% 0% / 0.16); + --shadow-2xl: 0px 2px 3px 0px hsl(0 0% 0% / 0.4); + + --tracking-normal: -0.025em; + --spacing: 0.27rem; +} + +.dark { + --background: oklch(0.23 0.01 278); + --foreground: oklch(0.95 0 0); + + --card: oklch(0.25 0.015 278); + --card-foreground: oklch(0.95 0 0); + + --popover: oklch(0.25 0.015 278); + --popover-foreground: oklch(0.95 0 0); + + --primary: oklch(0.56 0.17 280); + --primary-foreground: oklch(0.97 0 0); + + --secondary: oklch(0.35 0.03 280); + --secondary-foreground: oklch(0.92 0 0); + + --muted: oklch(0.33 0.02 280); + --muted-foreground: oklch(0.7 0.01 280); + + --accent: oklch(0.44 0.1 278); + --accent-foreground: oklch(0.88 0.09 280); + + --destructive: oklch(0.7 0.17 25); + --destructive-foreground: oklch(1 0 0); + + --border: oklch(0.34 0.02 278); + --input: oklch(0.34 0.02 278); + --ring: oklch(0.65 0.22 280); + --ring: oklch(0.56 0.17 280); + + --chart-1: oklch(0.68 0.15 275); + --chart-2: oklch(0.62 0.2 280); + --chart-3: oklch(0.7 0.14 292); + --chart-4: oklch(0.65 0.16 265); + --chart-5: oklch(0.72 0.1 285); + + --sidebar: oklch(0.2 0.01 278); + --sidebar-foreground: oklch(0.95 0 0); + --sidebar-primary: oklch(0.56 0.17 280); + --sidebar-primary-foreground: oklch(0.97 0 0); + --sidebar-accent: oklch(0.35 0.03 280); + --sidebar-accent-foreground: oklch(0.65 0.22 280); + --sidebar-border: oklch(0.34 0.02 278); + --sidebar-ring: oklch(0.65 0.22 280); +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); + + --font-sans: var(--font-sans); + --font-mono: var(--font-mono); + --font-serif: var(--font-serif); + + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + + --shadow-2xs: var(--shadow-2xs); + --shadow-xs: var(--shadow-xs); + --shadow-sm: var(--shadow-sm); + --shadow: var(--shadow); + --shadow-md: var(--shadow-md); + --shadow-lg: var(--shadow-lg); + --shadow-xl: var(--shadow-xl); + --shadow-2xl: var(--shadow-2xl); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + font-family: "Manrope", sans-serif; + letter-spacing: var(--tracking-normal); + } +} diff --git a/apps/main/static/favicon.png b/apps/main/static/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..822f477c02f9aa90349102051c37fe8b8e00abd9 GIT binary patch literal 303316 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񚑿`p-7SC7lL;C|q}Pt|51_`eVh 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 T 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℘KR6W@%RWH626TfFT$x~jq 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%0DUtME!~oNTk;9n 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 M1-! literal 0 HcmV?d00001 diff --git a/apps/main/static/images/avatar.png b/apps/main/static/images/avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..09892098aa943af5fe95e7dd434a07fe03e7ccd1 GIT binary patch literal 2011 zcmV<12PF83P)D!y%g4vZ*x1<2%*?Q`u)x5;+S=OH)zz}HvdGBD zw6wIs!oteR%FWHqrlzLJ$;r^r(7(UGt*x!9s;a}o!>FjJ#>U3Iy}i%R&$YF+x3{;h zuCCtR-nO>3udlDMv9Z$9(zv*|ySuyA*4EqG+ZlWNivR!y*-1n}RCr$O+*gj>NDPM2 zpNco<96RTn9rwSi8F&oCZFlz#Qq{xgJ4+x4LZrx&UjP6A00000000000000000000 z0000000000h)13Iv~gIg9X6)(&Ma2$kko6>k^LchuGJIe1?9VT%ih&$-<6da;(GtY zRygU`V`YKSGPJdX%aOK)>O)}bd#Gv~*uA$6-S28s*y-CA`a9YL;;WKvrF0c*ZKyul z)*h=`7wH^W2Elqsjqd}?Xz*Ptp%PkVVMQxo5?Bs`i9Upe<)onxU}?Eo>Ro7Cj@nv* ze0H1D=vvOYdIRQ`ySe^?YRPg}s_G9&F3o|+E;KJf72K|Qva4ddGB4O%8ki>o6?HI`i*6#+w#5|Y?<y|LwN+|wJ-DOs*c9lZ(lz;YDS^$r~880p|x z??Sy~IVsik0el`?F2Y>=KFtcZ1R zaGELyc&axa?H!NJ`jhg5v~%-y@6vvAx%c&^lgbGsy=v!Q)n20Y000000000000000 z0000001(G=0_9$3(l{U9f4Ydqf&CDSql-`X!}G?Z(<>_@q?M!EmqlnRgo`h=qe`mH zp^F!L#dKMP=0-j66q)X!m2_UKU< z?=gcl`PrjD-Z_UQ!Oo*l-Y$R@ac4on7M&O-j9L_~HG+QG8x@#l(9NwQst~Om6uTU# z#^D0B?jkA@-9e`x4^-yi2NWuOs^gbXXX{ZRZwp;qmr8Y=vc4+;?DuSiV=6Wdp+vB! zYWn~xgnKHt51~MOqTGjPD@PoaW3S(TV1qAdpLGPU;{}n+CNZ&z1&l zAk)yBhI<>K$Szko#25&!NK0f~qZEphQ8{ z3aIf)#a^Mvnrf|~$}^REhB95M)P*|TX3iCPq&kn#$aR|+5*6uFl|Ix;s7M0Ewp3#a z)kYpw@J3LsOsPOxLcOd<@p>5y2p=fiLkJUGpQ800tayqjR`dizEyTFw96Gb|` z!YX_?aV~Q~zz%VD}8+~-=U|TkA>Od+oZ}G}nbJRjS zi+DxREJj@nPrRDb5Owl07n$q)ia(q8)f$NXg*KhY?6EB-U9U5WqIxO65S z|C%@HT<{BxaK1b6FYI^o5WlAp7RhA6Kem`8MTkGt$mX+;7JpOgV>ZtqfL8IK*!Dm6 zzQ>>P{QXCNTMXj=+Nnb$0D%|)f;hy>mx(1hlm3f?BA8dZ4V=5bdfd6DDtbSk-BC3w tPksUb00000000000000000000fEW70#Mag`cL)Fg002ovPDHLkV1gNB=am2e literal 0 HcmV?d00001 diff --git a/apps/main/static/robots.txt b/apps/main/static/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/apps/main/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/apps/main/svelte.config.js b/apps/main/svelte.config.js new file mode 100644 index 0000000..f989453 --- /dev/null +++ b/apps/main/svelte.config.js @@ -0,0 +1,18 @@ +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; +import adapter from "svelte-adapter-bun"; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + // Consult https://svelte.dev/docs/kit/integrations + // for more information about preprocessors + preprocess: vitePreprocess(), + + kit: { + // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. + // See https://svelte.dev/docs/kit/adapters for more information about adapters. + adapter: adapter(), + }, +}; + +export default config; diff --git a/apps/main/tsconfig.json b/apps/main/tsconfig.json new file mode 100644 index 0000000..2c2ed3c --- /dev/null +++ b/apps/main/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "rewriteRelativeImportExtensions": true, + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + } + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files + // + // To make changes to top-level options such as include and exclude, we recommend extending + // the generated config; see https://svelte.dev/docs/kit/configuration#typescript +} diff --git a/apps/main/vite.config.ts b/apps/main/vite.config.ts new file mode 100644 index 0000000..e164584 --- /dev/null +++ b/apps/main/vite.config.ts @@ -0,0 +1,35 @@ +import { sveltekit } from "@sveltejs/kit/vite"; +import { defineConfig } from "vitest/config"; +import tailwindcss from "@tailwindcss/vite"; +import Icons from "unplugin-icons/vite"; +import { resolve } from "path"; + +export default defineConfig({ + plugins: [sveltekit(), tailwindcss(), Icons({ compiler: "svelte" })], + + resolve: { + alias: { + "@core": resolve(__dirname, "../../packages/logic/core"), + "@domains": resolve(__dirname, "../../packages/logic/domains"), + "@/core": resolve(__dirname, "../../packages/logic/core"), + "@/domains": resolve(__dirname, "../../packages/logic/domains"), + }, + }, + + test: { + expect: { requireAssertions: true }, + + projects: [ + { + extends: "./vite.config.ts", + + test: { + name: "server", + environment: "node", + include: ["src/**/*.{test,spec}.{js,ts}"], + exclude: ["src/**/*.svelte.{test,spec}.{js,ts}"], + }, + }, + ], + }, +}); diff --git a/apps/processor/.gitignore b/apps/processor/.gitignore new file mode 100644 index 0000000..506e4c3 --- /dev/null +++ b/apps/processor/.gitignore @@ -0,0 +1,2 @@ +# deps +node_modules/ diff --git a/apps/processor/README.md b/apps/processor/README.md new file mode 100644 index 0000000..6dd13e7 --- /dev/null +++ b/apps/processor/README.md @@ -0,0 +1,11 @@ +To install dependencies: +```sh +bun install +``` + +To run: +```sh +bun run dev +``` + +open http://localhost:3000 diff --git a/apps/processor/package.json b/apps/processor/package.json new file mode 100644 index 0000000..45056f9 --- /dev/null +++ b/apps/processor/package.json @@ -0,0 +1,13 @@ +{ + "name": "@app/processor", + "scripts": { + "dev": "bun run --hot src/index.ts", + "prod": "bun run src/index.ts" + }, + "dependencies": { + "hono": "^4.12.3" + }, + "devDependencies": { + "@types/bun": "latest" + } +} diff --git a/apps/processor/src/index.ts b/apps/processor/src/index.ts new file mode 100644 index 0000000..3191383 --- /dev/null +++ b/apps/processor/src/index.ts @@ -0,0 +1,9 @@ +import { Hono } from 'hono' + +const app = new Hono() + +app.get('/', (c) => { + return c.text('Hello Hono!') +}) + +export default app diff --git a/apps/processor/tsconfig.json b/apps/processor/tsconfig.json new file mode 100644 index 0000000..c442b33 --- /dev/null +++ b/apps/processor/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "strict": true, + "jsx": "react-jsx", + "jsxImportSource": "hono/jsx" + } +} \ No newline at end of file diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..c398850 --- /dev/null +++ b/bun.lock @@ -0,0 +1,1513 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "@becometaxpayer/invoicing-system", + "devDependencies": { + "prettier": "^3.8.1", + "prettier-plugin-sort-imports": "^1.8.11", + "prettier-plugin-svelte": "^3.5.0", + "prettier-plugin-tailwindcss": "^0.7.2", + "turbo": "^2.7.0", + "typescript": "^5.9.3", + }, + }, + "apps/main": { + "name": "@apps/main", + "version": "0.0.1", + "dependencies": { + "@pkg/db": "workspace:*", + "@pkg/logger": "workspace:*", + "@pkg/logic": "workspace:*", + "@pkg/result": "workspace:*", + "@tanstack/svelte-query": "^6.0.10", + "better-auth": "^1.4.7", + "date-fns": "^4.1.0", + "hono": "^4.11.1", + "marked": "^17.0.1", + "nanoid": "^5.1.6", + "neverthrow": "^8.2.0", + "qrcode": "^1.5.4", + "valibot": "^1.2.0", + }, + "devDependencies": { + "@iconify/json": "^2.2.434", + "@internationalized/date": "^3.10.0", + "@lucide/svelte": "^0.561.0", + "@sveltejs/adapter-auto": "^7.0.0", + "@sveltejs/kit": "^2.49.1", + "@sveltejs/vite-plugin-svelte": "^6.2.1", + "@tailwindcss/forms": "^0.5.10", + "@tailwindcss/typography": "^0.5.19", + "@tailwindcss/vite": "^4.1.18", + "@tanstack/table-core": "^8.21.3", + "@types/qrcode": "^1.5.6", + "bits-ui": "^2.14.4", + "clsx": "^2.1.1", + "embla-carousel-svelte": "^8.6.0", + "formsnap": "^2.0.1", + "layerchart": "2.0.0-next.43", + "mode-watcher": "^1.1.0", + "paneforge": "^1.0.2", + "prettier": "^3.7.4", + "prettier-plugin-svelte": "^3.4.0", + "prettier-plugin-tailwindcss": "^0.7.2", + "svelte": "^5.45.6", + "svelte-adapter-bun": "^1.0.1", + "svelte-check": "^4.3.4", + "svelte-sonner": "^1.0.7", + "sveltekit-superforms": "^2.28.1", + "tailwind-merge": "^3.4.0", + "tailwind-variants": "^3.2.2", + "tailwindcss": "^4.1.18", + "tw-animate-css": "^1.4.0", + "typescript": "^5.9.3", + "unplugin-icons": "^23.0.1", + "vaul-svelte": "^1.0.0-next.7", + "vite": "^7.2.6", + "vitest": "^4.0.15", + }, + }, + "apps/processor": { + "name": "@app/processor", + "dependencies": { + "hono": "^4.12.3", + }, + "devDependencies": { + "@types/bun": "latest", + }, + }, + "packages/db": { + "name": "@pkg/db", + "dependencies": { + "@pkg/settings": "workspace:*", + "dotenv": "^16.4.7", + "drizzle-orm": "^0.36.1", + "postgres": "^3.4.8", + }, + "devDependencies": { + "@types/bun": "latest", + "@types/pg": "^8.11.10", + "drizzle-kit": "^0.28.0", + }, + "peerDependencies": { + "typescript": "^5.0.0", + }, + }, + "packages/logger": { + "name": "@pkg/logger", + "dependencies": { + "@axiomhq/winston": "^1.3.1", + "@pkg/result": "workspace:*", + "@pkg/settings": "workspace:*", + "winston": "^3.17.0", + "winston-daily-rotate-file": "^5.0.0", + }, + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5.0.0", + }, + }, + "packages/logic": { + "name": "@pkg/logic", + "dependencies": { + "@hono/standard-validator": "^0.2.1", + "@pkg/db": "workspace:*", + "@pkg/logger": "workspace:*", + "@pkg/redis": "workspace:*", + "@pkg/result": "workspace:*", + "@pkg/settings": "workspace:*", + "@types/pdfkit": "^0.14.0", + "argon2": "^0.43.0", + "better-auth": "^1.4.7", + "date-fns-tz": "^3.2.0", + "dotenv": "^16.5.0", + "hono": "^4.11.1", + "imapflow": "^1.0.188", + "mailparser": "^3.7.3", + "nanoid": "^5.1.5", + "neverthrow": "^8.2.0", + "otplib": "^12.0.1", + "pdfkit": "^0.17.1", + "tmp": "^0.2.3", + "uuid": "^11.1.0", + "valibot": "^1.2.0", + "xlsx": "^0.18.5", + }, + "devDependencies": { + "@types/bun": "latest", + "@types/imapflow": "^1.0.22", + "@types/mailparser": "^3.4.6", + "@types/tmp": "^0.2.6", + "@types/uuid": "^10.0.0", + }, + "peerDependencies": { + "typescript": "^5.0.0", + }, + }, + "packages/redis": { + "name": "@pkg/redis", + "dependencies": { + "ioredis": "^5.6.1", + }, + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + "packages/result": { + "name": "@pkg/result", + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5.0.0", + }, + }, + "packages/settings": { + "name": "@pkg/settings", + "dependencies": { + "dotenv": "^17.2.3", + "valibot": "^1.2.0", + }, + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5.0.0", + }, + }, + }, + "packages": { + "@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="], + + "@app/processor": ["@app/processor@workspace:apps/processor"], + + "@apps/main": ["@apps/main@workspace:apps/main"], + + "@ark/schema": ["@ark/schema@0.56.0", "", { "dependencies": { "@ark/util": "0.56.0" } }, "sha512-ECg3hox/6Z/nLajxXqNhgPtNdHWC9zNsDyskwO28WinoFEnWow4IsERNz9AnXRhTZJnYIlAJ4uGn3nlLk65vZA=="], + + "@ark/util": ["@ark/util@0.56.0", "", {}, "sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA=="], + + "@axiomhq/js": ["@axiomhq/js@1.3.1", "", { "dependencies": { "fetch-retry": "^6.0.0", "uuid": "^11.0.2" } }, "sha512-Ytf5V3wKz8FKNiqJxnqZmUhjgJ7TItKUoyHVNE/H2V9dN1ozD6NNnsueenOjKdA48cm2sGRyP432nworst18aA=="], + + "@axiomhq/winston": ["@axiomhq/winston@1.3.1", "", { "dependencies": { "@axiomhq/js": "1.3.1", "winston-transport": "^4.5.0" } }, "sha512-Nad/PkMIkFp7VxJHnZQTg6co9QpTPQDRPeC+/G7BfvXjISQ5+AY+BxNV3h8St7Bs9tLLAufx+gtyG+fgS47x0Q=="], + + "@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="], + + "@better-auth/core": ["@better-auth/core@1.4.7", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "zod": "^4.1.12" }, "peerDependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "better-call": "1.1.5", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" } }, "sha512-rNfj8aNFwPwAMYo+ahoWDsqKrV7svD3jhHSC6+A77xxKodbgV0UgH+RO21GMaZ0PPAibEl851nw5e3bsNslW/w=="], + + "@better-auth/telemetry": ["@better-auth/telemetry@1.4.7", "", { "dependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21" }, "peerDependencies": { "@better-auth/core": "1.4.7" } }, "sha512-k07C/FWnX6m+IxLruNkCweIxuaIwVTB2X40EqwamRVhYNBAhOYZFGLHH+PtQyM+Yf1Z4+8H6MugLOXSreXNAjQ=="], + + "@better-auth/utils": ["@better-auth/utils@0.3.0", "", {}, "sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw=="], + + "@better-fetch/fetch": ["@better-fetch/fetch@1.1.21", "", {}, "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A=="], + + "@colors/colors": ["@colors/colors@1.6.0", "", {}, "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA=="], + + "@dabh/diagnostics": ["@dabh/diagnostics@2.0.8", "", { "dependencies": { "@so-ric/colorspace": "^1.1.6", "enabled": "2.0.x", "kuler": "^2.0.0" } }, "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q=="], + + "@dagrejs/dagre": ["@dagrejs/dagre@1.1.8", "", { "dependencies": { "@dagrejs/graphlib": "2.2.4" } }, "sha512-5SEDlndt4W/LaVzPYJW+bSmSEZc9EzTf8rJ20WCKvjS5EAZAN0b+x0Yww7VMT4R3Wootkg+X9bUfUxazYw6Blw=="], + + "@dagrejs/graphlib": ["@dagrejs/graphlib@2.2.4", "", {}, "sha512-mepCf/e9+SKYy1d02/UkvSy6+6MoyXhVxP8lLDfA7BPE1X1d4dR0sZznmbM8/XVJ1GPM+Svnx7Xj6ZweByWUkw=="], + + "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], + + "@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + + "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], + + "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.2", "", { "os": "android", "cpu": "arm64" }, "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.2", "", { "os": "android", "cpu": "x64" }, "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.2", "", { "os": "linux", "cpu": "arm" }, "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.2", "", { "os": "linux", "cpu": "x64" }, "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.2", "", { "os": "none", "cpu": "x64" }, "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="], + + "@exodus/schemasafe": ["@exodus/schemasafe@1.3.0", "", {}, "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw=="], + + "@floating-ui/core": ["@floating-ui/core@1.7.3", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w=="], + + "@floating-ui/dom": ["@floating-ui/dom@1.7.4", "", { "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" } }, "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA=="], + + "@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="], + + "@hapi/hoek": ["@hapi/hoek@9.3.0", "", {}, "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ=="], + + "@hapi/topo": ["@hapi/topo@5.1.0", "", { "dependencies": { "@hapi/hoek": "^9.0.0" } }, "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg=="], + + "@hono/standard-validator": ["@hono/standard-validator@0.2.1", "", { "peerDependencies": { "@standard-schema/spec": "1.0.0", "hono": ">=3.9.0" } }, "sha512-uF1W7/iSWi0r5Mugj5ZzdlCVp/KeGatS6JYt+Eht9xKO0IDtdZbdaLApgjrzHINQQa+Wnxw2pcX6EfO+vgB+Wg=="], + + "@iconify/json": ["@iconify/json@2.2.434", "", { "dependencies": { "@iconify/types": "*", "pathe": "^2.0.3" } }, "sha512-iTG1M1OCzHnfW+VZbsyZyLfttR0EB5SurUq92zoPkmI+QAsh23tp8ocR4umUMsRMFkz448MoC5iabA+EYDU46w=="], + + "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], + + "@iconify/utils": ["@iconify/utils@3.1.0", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "mlly": "^1.8.0" } }, "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw=="], + + "@img/colour": ["@img/colour@1.0.0", "", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="], + + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], + + "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="], + + "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="], + + "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="], + + "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="], + + "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="], + + "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="], + + "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="], + + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="], + + "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="], + + "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="], + + "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="], + + "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="], + + "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="], + + "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="], + + "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="], + + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="], + + "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="], + + "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="], + + "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="], + + "@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="], + + "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="], + + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="], + + "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], + + "@internationalized/date": ["@internationalized/date@3.10.1", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-oJrXtQiAXLvT9clCf1K4kxp3eKsQhIaZqxEyowkBcsvZDdZkbWrVmnGknxs5flTD0VGsxrxKgBCZty1EzoiMzA=="], + + "@ioredis/commands": ["@ioredis/commands@1.4.0", "", {}, "sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@layerstack/svelte-actions": ["@layerstack/svelte-actions@1.0.1-next.14", "", { "dependencies": { "@floating-ui/dom": "^1.7.0", "@layerstack/utils": "2.0.0-next.14", "d3-scale": "^4.0.2" } }, "sha512-MPBmVaB+GfNHvBkg5nJkPG18smoXKvsvJRpsdWnrUBfca+TieZLoaEzNxDH+9LG11dIXP9gghsXt1mUqbbyAsA=="], + + "@layerstack/svelte-state": ["@layerstack/svelte-state@0.1.0-next.19", "", { "dependencies": { "@layerstack/utils": "2.0.0-next.14" } }, "sha512-yCYoQAIbeP8y1xmOB/r0+UundgP4JFnpNURgMki+26TotzoqrZ5oLpHvhPSVm60ks+buR3ebDBTeUFdHzxwzQQ=="], + + "@layerstack/tailwind": ["@layerstack/tailwind@2.0.0-next.17", "", { "dependencies": { "@layerstack/utils": "^2.0.0-next.14", "clsx": "^2.1.1", "d3-array": "^3.2.4", "lodash-es": "^4.17.21", "tailwind-merge": "^3.2.0" } }, "sha512-ZSn6ouqpnzB6DKzSKLVwrUBOQsrzpDA/By2/ba9ApxgTGnaD1nyqNwrvmZ+kswdAwB4YnrGEAE4VZkKrB2+DaQ=="], + + "@layerstack/utils": ["@layerstack/utils@2.0.0-next.14", "", { "dependencies": { "d3-array": "^3.2.4", "d3-time": "^3.1.0", "d3-time-format": "^4.1.0", "lodash-es": "^4.17.21" } }, "sha512-1I2CS0Cwgs53W35qVg1eBdYhB/CiPvL3s0XE61b8jWkTHxgjBF65yYNgXjW74kv7WI7GsJcWMNBufPd0rnu9kA=="], + + "@lucide/svelte": ["@lucide/svelte@0.561.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-vofKV2UFVrKE6I4ewKJ3dfCXSV6iP6nWVmiM83MLjsU91EeJcEg7LoWUABLp/aOTxj1HQNbJD1f3g3L0JQgH9A=="], + + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], + + "@neondatabase/serverless": ["@neondatabase/serverless@1.0.2", "", { "dependencies": { "@types/node": "^22.15.30", "@types/pg": "^8.8.0" } }, "sha512-I5sbpSIAHiB+b6UttofhrN/UJXII+4tZPAq1qugzwCwLIL8EZLV7F/JyHUrEIiGgQpEXzpnjlJ+zwcEhheGvCw=="], + + "@next/env": ["@next/env@15.5.9", "", {}, "sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg=="], + + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw=="], + + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.5.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg=="], + + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.5.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA=="], + + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.5.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw=="], + + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.5.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw=="], + + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.5.7", "", { "os": "linux", "cpu": "x64" }, "sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA=="], + + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.5.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ=="], + + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.5.7", "", { "os": "win32", "cpu": "x64" }, "sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw=="], + + "@noble/ciphers": ["@noble/ciphers@2.1.1", "", {}, "sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw=="], + + "@noble/hashes": ["@noble/hashes@2.0.1", "", {}, "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw=="], + + "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], + + "@otplib/core": ["@otplib/core@12.0.1", "", {}, "sha512-4sGntwbA/AC+SbPhbsziRiD+jNDdIzsZ3JUyfZwjtKyc/wufl1pnSIaG4Uqx8ymPagujub0o92kgBnB89cuAMA=="], + + "@otplib/plugin-crypto": ["@otplib/plugin-crypto@12.0.1", "", { "dependencies": { "@otplib/core": "^12.0.1" } }, "sha512-qPuhN3QrT7ZZLcLCyKOSNhuijUi9G5guMRVrxq63r9YNOxxQjPm59gVxLM+7xGnHnM6cimY57tuKsjK7y9LM1g=="], + + "@otplib/plugin-thirty-two": ["@otplib/plugin-thirty-two@12.0.1", "", { "dependencies": { "@otplib/core": "^12.0.1", "thirty-two": "^1.0.2" } }, "sha512-MtT+uqRso909UkbrrYpJ6XFjj9D+x2Py7KjTO9JDPhL0bJUYVu5kFP4TFZW4NFAywrAtFRxOVY261u0qwb93gA=="], + + "@otplib/preset-default": ["@otplib/preset-default@12.0.1", "", { "dependencies": { "@otplib/core": "^12.0.1", "@otplib/plugin-crypto": "^12.0.1", "@otplib/plugin-thirty-two": "^12.0.1" } }, "sha512-xf1v9oOJRyXfluBhMdpOkr+bsE+Irt+0D5uHtvg6x1eosfmHCsCC6ej/m7FXiWqdo0+ZUI6xSKDhJwc8yfiOPQ=="], + + "@otplib/preset-v11": ["@otplib/preset-v11@12.0.1", "", { "dependencies": { "@otplib/core": "^12.0.1", "@otplib/plugin-crypto": "^12.0.1", "@otplib/plugin-thirty-two": "^12.0.1" } }, "sha512-9hSetMI7ECqbFiKICrNa4w70deTUfArtwXykPUvSHWOdzOlfa9ajglu7mNCntlvxycTiOAXkQGwjQCzzDEMRMg=="], + + "@oxc-project/runtime": ["@oxc-project/runtime@0.71.0", "", {}, "sha512-QwoF5WUXIGFQ+hSxWEib4U/aeLoiDN9JlP18MnBgx9LLPRDfn1iICtcow7Jgey6HLH4XFceWXQD5WBJ39dyJcw=="], + + "@oxc-project/types": ["@oxc-project/types@0.71.0", "", {}, "sha512-5CwQ4MI+P4MQbjLWXgNurA+igGwu/opNetIE13LBs9+V93R64MLvDKOOLZIXSzEfovU3Zef3q3GjPnMTgJTn2w=="], + + "@phc/format": ["@phc/format@1.0.0", "", {}, "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ=="], + + "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="], + + "@pkg/db": ["@pkg/db@workspace:packages/db"], + + "@pkg/logger": ["@pkg/logger@workspace:packages/logger"], + + "@pkg/logic": ["@pkg/logic@workspace:packages/logic"], + + "@pkg/redis": ["@pkg/redis@workspace:packages/redis"], + + "@pkg/result": ["@pkg/result@workspace:packages/result"], + + "@pkg/settings": ["@pkg/settings@workspace:packages/settings"], + + "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], + + "@poppinss/macroable": ["@poppinss/macroable@1.1.0", "", {}, "sha512-y/YKzZDuG8XrpXpM7Z1RdQpiIc0MAKyva24Ux1PB4aI7RiSI/79K8JVDcdyubriTm7vJ1LhFs8CrZpmPnx/8Pw=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-beta.9-commit.d91dfb5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Mp0/gqiPdepHjjVm7e0yL1acWvI0rJVVFQEADSezvAjon9sjQ7CEg9JnXICD4B1YrPmN9qV/e7cQZCp87tTV4w=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-beta.9-commit.d91dfb5", "", { "os": "darwin", "cpu": "x64" }, "sha512-40re4rMNrsi57oavRzIOpRGmg3QRlW6Ea8Q3znaqgOuJuKVrrm2bIQInTfkZJG7a4/5YMX7T951d0+toGLTdCA=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-beta.9-commit.d91dfb5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-8BDM939bbMariZupiHp3OmP5N+LXPT4mULA0hZjDaq970PCxv4krZOSMG+HkWUUwmuQROtV+/00xw39EO0P+8g=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9-commit.d91dfb5", "", { "os": "linux", "cpu": "arm" }, "sha512-sntsPaPgrECpBB/+2xrQzVUt0r493TMPI+4kWRMhvMsmrxOqH1Ep5lM0Wua/ZdbfZNwm1aVa5pcESQfNfM4Fhw=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9-commit.d91dfb5", "", { "os": "linux", "cpu": "arm64" }, "sha512-5clBW/I+er9F2uM1OFjJFWX86y7Lcy0M+NqsN4s3o07W+8467Zk8oQa4B45vdaXoNUF/yqIAgKkA/OEdQDxZqA=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-beta.9-commit.d91dfb5", "", { "os": "linux", "cpu": "arm64" }, "sha512-wv+rnAfQDk9p/CheX8/Kmqk2o1WaFa4xhWI9gOyDMk/ljvOX0u0ubeM8nI1Qfox7Tnh71eV5AjzSePXUhFOyOg=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-beta.9-commit.d91dfb5", "", { "os": "linux", "cpu": "x64" }, "sha512-gxD0/xhU4Py47IH3bKZbWtvB99tMkUPGPJFRfSc5UB9Osoje0l0j1PPbxpUtXIELurYCqwLBKXIMTQGifox1BQ=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-beta.9-commit.d91dfb5", "", { "os": "linux", "cpu": "x64" }, "sha512-HotuVe3XUjDwqqEMbm3o3IRkP9gdm8raY/btd/6KE3JGLF/cv4+3ff1l6nOhAZI8wulWDPEXPtE7v+HQEaTXnA=="], + + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-beta.9-commit.d91dfb5", "", { "dependencies": { "@napi-rs/wasm-runtime": "^0.2.4" }, "cpu": "none" }, "sha512-8Cx+ucbd8n2dIr21FqBh6rUvTVL0uTgEtKR7l+MUZ5BgY4dFh1e4mPVX8oqmoYwOxBiXrsD2JIOCz4AyKLKxWA=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9-commit.d91dfb5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Vhq5vikrVDxAa75fxsyqj0c0Y/uti/TwshXI71Xb8IeUQJOBnmLUsn5dgYf5ljpYYkNa0z9BPAvUDIDMmyDi+w=="], + + "@rolldown/binding-win32-ia32-msvc": ["@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9-commit.d91dfb5", "", { "os": "win32", "cpu": "ia32" }, "sha512-lN7RIg9Iugn08zP2aZN9y/MIdG8iOOCE93M1UrFlrxMTqPf8X+fDzmR/OKhTSd1A2pYNipZHjyTcb5H8kyQSow=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-beta.9-commit.d91dfb5", "", { "os": "win32", "cpu": "x64" }, "sha512-7/7cLIn48Y+EpQ4CePvf8reFl63F15yPUlg4ZAhl+RXJIfydkdak1WD8Ir3AwAO+bJBXzrfNL+XQbxm0mcQZmw=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.9-commit.d91dfb5", "", {}, "sha512-8sExkWRK+zVybw3+2/kBkYBFeLnEUWz1fT7BLHplpzmtqkOfTbAQ9gkt4pzwGIIZmg4Qn5US5ACjUBenrhezwQ=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.54.0", "", { "os": "android", "cpu": "arm" }, "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.54.0", "", { "os": "android", "cpu": "arm64" }, "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.54.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.54.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.54.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.54.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.54.0", "", { "os": "linux", "cpu": "arm" }, "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.54.0", "", { "os": "linux", "cpu": "arm" }, "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.54.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.54.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.54.0", "", { "os": "linux", "cpu": "none" }, "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.54.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.54.0", "", { "os": "linux", "cpu": "none" }, "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.54.0", "", { "os": "linux", "cpu": "none" }, "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.54.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.54.0", "", { "os": "linux", "cpu": "x64" }, "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.54.0", "", { "os": "linux", "cpu": "x64" }, "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.54.0", "", { "os": "none", "cpu": "arm64" }, "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.54.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.54.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.54.0", "", { "os": "win32", "cpu": "x64" }, "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.54.0", "", { "os": "win32", "cpu": "x64" }, "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg=="], + + "@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="], + + "@sideway/address": ["@sideway/address@4.1.5", "", { "dependencies": { "@hapi/hoek": "^9.0.0" } }, "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q=="], + + "@sideway/formula": ["@sideway/formula@3.0.1", "", {}, "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg=="], + + "@sideway/pinpoint": ["@sideway/pinpoint@2.0.0", "", {}, "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="], + + "@so-ric/colorspace": ["@so-ric/colorspace@1.1.6", "", { "dependencies": { "color": "^5.0.2", "text-hex": "1.0.x" } }, "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.8", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-esgN+54+q0NjB0Y/4BomT9samII7jGwNy/2a3wNZbT2A2RpmXsXwUt24LvLhx6jUq2gVk4cWEvcRO6MFQbOfNA=="], + + "@sveltejs/adapter-auto": ["@sveltejs/adapter-auto@7.0.0", "", { "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "sha512-ImDWaErTOCkRS4Gt+5gZuymKFBobnhChXUZ9lhUZLahUgvA4OOvRzi3sahzYgbxGj5nkA6OV0GAW378+dl/gyw=="], + + "@sveltejs/kit": ["@sveltejs/kit@2.49.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.3.2", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["@opentelemetry/api"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-Vp3zX/qlwerQmHMP6x0Ry1oY7eKKRcOWGc2P59srOp4zcqyn+etJyQpELgOi4+ZSUgteX8Y387NuwruLgGXLUQ=="], + + "@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@6.2.1", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "debug": "^4.4.1", "deepmerge": "^4.3.1", "magic-string": "^0.30.17", "vitefu": "^1.1.1" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ=="], + + "@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@5.0.1", "", { "dependencies": { "debug": "^4.4.1" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA=="], + + "@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="], + + "@tailwindcss/forms": ["@tailwindcss/forms@0.5.11", "", { "dependencies": { "mini-svg-data-uri": "^1.2.3" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" } }, "sha512-h9wegbZDPurxG22xZSoWtdzc41/OlNEUQERNqI/0fOwa2aVlWGu7C35E/x6LDyD3lgtztFSSjKZyuVM0hxhbgA=="], + + "@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.18", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.18", "@tailwindcss/oxide-darwin-arm64": "4.1.18", "@tailwindcss/oxide-darwin-x64": "4.1.18", "@tailwindcss/oxide-freebsd-x64": "4.1.18", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", "@tailwindcss/oxide-linux-x64-musl": "4.1.18", "@tailwindcss/oxide-wasm32-wasi": "4.1.18", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" } }, "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.18", "", { "os": "android", "cpu": "arm64" }, "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.18", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18", "", { "os": "linux", "cpu": "arm" }, "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.18", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.0", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.18", "", { "os": "win32", "cpu": "x64" }, "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q=="], + + "@tailwindcss/typography": ["@tailwindcss/typography@0.5.19", "", { "dependencies": { "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg=="], + + "@tailwindcss/vite": ["@tailwindcss/vite@4.1.18", "", { "dependencies": { "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "tailwindcss": "4.1.18" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA=="], + + "@tanstack/query-core": ["@tanstack/query-core@5.90.12", "", {}, "sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg=="], + + "@tanstack/svelte-query": ["@tanstack/svelte-query@6.0.10", "", { "dependencies": { "@tanstack/query-core": "5.90.12" }, "peerDependencies": { "svelte": "^5.25.0" } }, "sha512-J0kM3JNvRcRCM6cbHLeICs73aLp98N/nsihdVEtiNo3MEN4pAnO45qZ2yxX70MrEZ9vffXaCXMCChwgXs1lZ/Q=="], + + "@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="], + + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="], + + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + + "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], + + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/imapflow": ["@types/imapflow@1.0.189", "", { "dependencies": { "imapflow": "*" } }, "sha512-3I/9/rKYd4+p4tAkQ3sRtTu2EazHzvruzwgpjpM6ZLx8YHjUyqPv3rBEg5wabVWuuX7/+yHULYROQz5cgALX0g=="], + + "@types/mailparser": ["@types/mailparser@3.4.6", "", { "dependencies": { "@types/node": "*", "iconv-lite": "^0.6.3" } }, "sha512-wVV3cnIKzxTffaPH8iRnddX1zahbYB1ZEoAxyhoBo3TBCBuK6nZ8M8JYO/RhsCuuBVOw/DEN/t/ENbruwlxn6Q=="], + + "@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="], + + "@types/pdfkit": ["@types/pdfkit@0.14.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-X94hoZVr9dNfV23roeXRm57AWS+AOMak3gq2wZvn4TXiLvXE8+TrYaM5IkMyZbGRw49jEqI49rP/UVL3+C3Svg=="], + + "@types/pg": ["@types/pg@8.16.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ=="], + + "@types/qrcode": ["@types/qrcode@1.5.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw=="], + + "@types/react": ["@types/react@19.2.10", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw=="], + + "@types/tmp": ["@types/tmp@0.2.6", "", {}, "sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA=="], + + "@types/triple-beam": ["@types/triple-beam@1.3.5", "", {}, "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw=="], + + "@types/uuid": ["@types/uuid@10.0.0", "", {}, "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ=="], + + "@types/validator": ["@types/validator@13.15.10", "", {}, "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA=="], + + "@typeschema/class-validator": ["@typeschema/class-validator@0.3.0", "", { "dependencies": { "@typeschema/core": "0.14.0" }, "peerDependencies": { "class-validator": "^0.14.1" }, "optionalPeers": ["class-validator"] }, "sha512-OJSFeZDIQ8EK1HTljKLT5CItM2wsbgczLN8tMEfz3I1Lmhc5TBfkZ0eikFzUC16tI3d1Nag7um6TfCgp2I2Bww=="], + + "@typeschema/core": ["@typeschema/core@0.14.0", "", { "peerDependencies": { "@types/json-schema": "^7.0.15" }, "optionalPeers": ["@types/json-schema"] }, "sha512-Ia6PtZHcL3KqsAWXjMi5xIyZ7XMH4aSnOQes8mfMLx+wGFGtGRNlwe6Y7cYvX+WfNK67OL0/HSe9t8QDygV0/w=="], + + "@valibot/to-json-schema": ["@valibot/to-json-schema@1.5.0", "", { "peerDependencies": { "valibot": "^1.2.0" } }, "sha512-GE7DmSr1C2UCWPiV0upRH6mv0cCPsqYGs819fb6srCS1tWhyXrkGGe+zxUiwzn/L1BOfADH4sNjY/YHCuP8phQ=="], + + "@vinejs/compiler": ["@vinejs/compiler@3.0.0", "", {}, "sha512-v9Lsv59nR56+bmy2p0+czjZxsLHwaibJ+SV5iK9JJfehlJMa501jUJQqqz4X/OqKXrxtE3uTQmSqjUqzF3B2mw=="], + + "@vinejs/vine": ["@vinejs/vine@3.0.1", "", { "dependencies": { "@poppinss/macroable": "^1.0.4", "@types/validator": "^13.12.2", "@vinejs/compiler": "^3.0.0", "camelcase": "^8.0.0", "dayjs": "^1.11.13", "dlv": "^1.1.3", "normalize-url": "^8.0.1", "validator": "^13.12.0" } }, "sha512-ZtvYkYpZOYdvbws3uaOAvTFuvFXoQGAtmzeiXu+XSMGxi5GVsODpoI9Xu9TplEMuD/5fmAtBbKb9cQHkWkLXDQ=="], + + "@vitest/expect": ["@vitest/expect@4.0.16", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.16", "@vitest/utils": "4.0.16", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA=="], + + "@vitest/mocker": ["@vitest/mocker@4.0.16", "", { "dependencies": { "@vitest/spy": "4.0.16", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@4.0.16", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA=="], + + "@vitest/runner": ["@vitest/runner@4.0.16", "", { "dependencies": { "@vitest/utils": "4.0.16", "pathe": "^2.0.3" } }, "sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q=="], + + "@vitest/snapshot": ["@vitest/snapshot@4.0.16", "", { "dependencies": { "@vitest/pretty-format": "4.0.16", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA=="], + + "@vitest/spy": ["@vitest/spy@4.0.16", "", {}, "sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw=="], + + "@vitest/utils": ["@vitest/utils@4.0.16", "", { "dependencies": { "@vitest/pretty-format": "4.0.16", "tinyrainbow": "^3.0.3" } }, "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA=="], + + "@zone-eu/mailsplit": ["@zone-eu/mailsplit@5.4.8", "", { "dependencies": { "libbase64": "1.3.0", "libmime": "5.3.7", "libqp": "2.1.1" } }, "sha512-eEyACj4JZ7sjzRvy26QhLgKEMWwQbsw1+QZnlLX+/gihcNH07lVPOcnwf5U6UAL7gkc//J3jVd76o/WS+taUiA=="], + + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + + "adler-32": ["adler-32@1.3.1", "", {}, "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "ansis": ["ansis@4.2.0", "", {}, "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig=="], + + "argon2": ["argon2@0.43.1", "", { "dependencies": { "@phc/format": "^1.0.0", "node-addon-api": "^8.4.0", "node-gyp-build": "^4.8.4" } }, "sha512-TfOzvDWUaQPurCT1hOwIeFNkgrAJDpbBGBGWDgzDsm11nNhImc13WhdGdCU6K7brkp8VpeY07oGtSex0Wmhg8w=="], + + "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], + + "arkregex": ["arkregex@0.0.5", "", { "dependencies": { "@ark/util": "0.56.0" } }, "sha512-ncYjBdLlh5/QnVsAA8De16Tc9EqmYM7y/WU9j+236KcyYNUXogpz3sC4ATIZYzzLxwI+0sEOaQLEmLmRleaEXw=="], + + "arktype": ["arktype@2.1.29", "", { "dependencies": { "@ark/schema": "0.56.0", "@ark/util": "0.56.0", "arkregex": "0.0.5" } }, "sha512-jyfKk4xIOzvYNayqnD8ZJQqOwcrTOUbIU4293yrzAjA3O1dWh61j71ArMQ6tS/u4pD7vabSPe7nG3RCyoXW6RQ=="], + + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + + "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], + + "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="], + + "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], + + "base64-js": ["base64-js@0.0.8", "", {}, "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw=="], + + "better-auth": ["better-auth@1.4.7", "", { "dependencies": { "@better-auth/core": "1.4.7", "@better-auth/telemetry": "1.4.7", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "@noble/ciphers": "^2.0.0", "@noble/hashes": "^2.0.0", "better-call": "1.1.5", "defu": "^6.1.4", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1", "zod": "^4.1.12" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.22.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "better-sqlite3": "^12.4.1", "drizzle-kit": "^0.31.4", "drizzle-orm": "^0.41.0", "mongodb": "^6.18.0", "mysql2": "^3.14.4", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.16.3", "prisma": "^5.22.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^4.0.15", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-kVmDQxzqGwP4FFMOYpS5I7oAaoFW3hwooUAAtcbb2DrOYv5EUvRUDJbTMaPoMTj7URjNDQ6vG9gcCS1Q+0aVBw=="], + + "better-call": ["better-call@1.1.5", "", { "dependencies": { "@better-auth/utils": "^0.3.0", "@better-fetch/fetch": "^1.1.4", "rou3": "^0.7.10", "set-cookie-parser": "^2.7.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-nQJ3S87v6wApbDwbZ++FrQiSiVxWvZdjaO+2v6lZJAG2WWggkB2CziUDjPciz3eAt9TqfRursIQMZIcpkBnvlw=="], + + "bits-ui": ["bits-ui@2.14.4", "", { "dependencies": { "@floating-ui/core": "^1.7.1", "@floating-ui/dom": "^1.7.1", "esm-env": "^1.1.2", "runed": "^0.35.1", "svelte-toolbelt": "^0.10.6", "tabbable": "^6.2.0" }, "peerDependencies": { "@internationalized/date": "^3.8.1", "svelte": "^5.33.0" } }, "sha512-W6kenhnbd/YVvur+DKkaVJ6GldE53eLewur5AhUCqslYQ0vjZr8eWlOfwZnMiPB+PF5HMVqf61vXBvmyrAmPWg=="], + + "brotli": ["brotli@1.3.3", "", { "dependencies": { "base64-js": "^1.1.2" } }, "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + + "bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="], + + "camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001761", "", {}, "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g=="], + + "cfb": ["cfb@1.2.2", "", { "dependencies": { "adler-32": "~1.3.0", "crc-32": "~1.2.0" } }, "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA=="], + + "chai": ["chai@6.2.1", "", {}, "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg=="], + + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "class-validator": ["class-validator@0.14.3", "", { "dependencies": { "@types/validator": "^13.15.3", "libphonenumber-js": "^1.11.1", "validator": "^13.15.20" } }, "sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA=="], + + "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="], + + "cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="], + + "clone": ["clone@2.1.2", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="], + + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + + "cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="], + + "codepage": ["codepage@1.15.0", "", {}, "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA=="], + + "color": ["color@5.0.3", "", { "dependencies": { "color-convert": "^3.1.3", "color-string": "^2.1.3" } }, "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA=="], + + "color-convert": ["color-convert@3.1.3", "", { "dependencies": { "color-name": "^2.0.0" } }, "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg=="], + + "color-name": ["color-name@2.1.0", "", {}, "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg=="], + + "color-string": ["color-string@2.1.4", "", { "dependencies": { "color-name": "^2.0.0" } }, "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg=="], + + "commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], + + "confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="], + + "cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="], + + "crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="], + + "crypto-js": ["crypto-js@4.2.0", "", {}, "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="], + + "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], + + "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], + + "d3-delaunay": ["d3-delaunay@6.0.4", "", { "dependencies": { "delaunator": "5" } }, "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A=="], + + "d3-dispatch": ["d3-dispatch@3.0.1", "", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="], + + "d3-dsv": ["d3-dsv@3.0.1", "", { "dependencies": { "commander": "7", "iconv-lite": "0.6", "rw": "1" }, "bin": { "csv2json": "bin/dsv2json.js", "csv2tsv": "bin/dsv2dsv.js", "dsv2dsv": "bin/dsv2dsv.js", "dsv2json": "bin/dsv2json.js", "json2csv": "bin/json2dsv.js", "json2dsv": "bin/json2dsv.js", "json2tsv": "bin/json2dsv.js", "tsv2csv": "bin/dsv2dsv.js", "tsv2json": "bin/dsv2json.js" } }, "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q=="], + + "d3-force": ["d3-force@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", "d3-timer": "1 - 3" } }, "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg=="], + + "d3-format": ["d3-format@3.1.0", "", {}, "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA=="], + + "d3-geo": ["d3-geo@3.1.1", "", { "dependencies": { "d3-array": "2.5.0 - 3" } }, "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q=="], + + "d3-geo-voronoi": ["d3-geo-voronoi@2.1.0", "", { "dependencies": { "d3-array": "3", "d3-delaunay": "6", "d3-geo": "3", "d3-tricontour": "1" } }, "sha512-kqE4yYuOjPbKdBXG0xztCacPwkVSK2REF1opSNrnqqtXJmNcM++UbwQ8SxvwP6IQTj9RvIjjK4qeiVsEfj0Z2Q=="], + + "d3-hierarchy": ["d3-hierarchy@3.1.2", "", {}, "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA=="], + + "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="], + + "d3-interpolate-path": ["d3-interpolate-path@2.3.0", "", {}, "sha512-tZYtGXxBmbgHsIc9Wms6LS5u4w6KbP8C09a4/ZYc4KLMYYqub57rRBUgpUr2CIarIrJEpdAWWxWQvofgaMpbKQ=="], + + "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="], + + "d3-quadtree": ["d3-quadtree@3.0.1", "", {}, "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw=="], + + "d3-random": ["d3-random@3.0.1", "", {}, "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ=="], + + "d3-sankey": ["d3-sankey@0.12.3", "", { "dependencies": { "d3-array": "1 - 2", "d3-shape": "^1.2.0" } }, "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ=="], + + "d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="], + + "d3-scale-chromatic": ["d3-scale-chromatic@3.1.0", "", { "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" } }, "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ=="], + + "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="], + + "d3-tile": ["d3-tile@1.0.0", "", {}, "sha512-79fnTKpPMPDS5xQ0xuS9ir0165NEwwkFpe/DSOmc2Gl9ldYzKKRDWogmTTE8wAJ8NA7PMapNfEcyKhI9Lxdu5Q=="], + + "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="], + + "d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="], + + "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], + + "d3-tricontour": ["d3-tricontour@1.1.0", "", { "dependencies": { "d3-delaunay": "6", "d3-scale": "4" } }, "sha512-G7gHKj89n2owmkGb6WX6ixcnQ0Kf/0wpa9VIh9DGdbHu8wdrlaHU4ir3/bFNERl8N8nn4G7e7qbtBG8N9caihQ=="], + + "date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="], + + "date-fns-tz": ["date-fns-tz@3.2.0", "", { "peerDependencies": { "date-fns": "^3.0.0 || ^4.0.0" } }, "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ=="], + + "dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="], + + "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], + + "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + + "delaunator": ["delaunator@5.0.1", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw=="], + + "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "devalue": ["devalue@5.6.1", "", {}, "sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A=="], + + "dfa": ["dfa@1.2.0", "", {}, "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q=="], + + "dijkstrajs": ["dijkstrajs@1.0.3", "", {}, "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="], + + "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], + + "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], + + "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], + + "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], + + "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], + + "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], + + "drizzle-kit": ["drizzle-kit@0.28.1", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.19.7", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-JimOV+ystXTWMgZkLHYHf2w3oS28hxiH1FR0dkmJLc7GHzdGJoJAQtQS5DRppnabsRZwE2U1F6CuezVBgmsBBQ=="], + + "drizzle-orm": ["drizzle-orm@0.36.4", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=3", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/react": ">=18", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "react": ">=18", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/react", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "knex", "kysely", "mysql2", "pg", "postgres", "react", "sql.js", "sqlite3"] }, "sha512-1OZY3PXD7BR00Gl61UUOFihslDldfH4NFRH2MbP54Yxi0G/PKn4HfO65JYZ7c16DeP3SpM3Aw+VXVG9j6CRSXA=="], + + "effect": ["effect@3.19.13", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-8MZ783YuHRwHZX2Mmm+bpGxq+7XPd88sWwYAz2Ysry80sEKpftDZXs2Hg9ZyjESi1IBTNHF0oDKe0zJRkUlyew=="], + + "embla-carousel": ["embla-carousel@8.6.0", "", {}, "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="], + + "embla-carousel-reactive-utils": ["embla-carousel-reactive-utils@8.6.0", "", { "peerDependencies": { "embla-carousel": "8.6.0" } }, "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A=="], + + "embla-carousel-svelte": ["embla-carousel-svelte@8.6.0", "", { "dependencies": { "embla-carousel": "8.6.0", "embla-carousel-reactive-utils": "8.6.0" }, "peerDependencies": { "svelte": "^3.49.0 || ^4.0.0 || ^5.0.0" } }, "sha512-ZDsKk8Sdv+AUTygMYcwZjfRd1DTh+JSUzxkOo8b9iKAkYjg+39mzbY/lwHsE3jXSpKxdKWS69hPSNuzlOGtR2Q=="], + + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "enabled": ["enabled@2.0.0", "", {}, "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="], + + "encoding-japanese": ["encoding-japanese@2.2.0", "", {}, "sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A=="], + + "enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="], + + "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], + + "esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], + + "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], + + "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="], + + "esrap": ["esrap@2.2.1", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-GiYWG34AN/4CUyaWAgunGt0Rxvr1PTMlGC0vvEov/uOQYWne2bpN03Um+k8jT+q3op33mKouP2zeJ6OlM+qeUg=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + + "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], + + "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fecha": ["fecha@4.2.3", "", {}, "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="], + + "fetch-retry": ["fetch-retry@6.0.0", "", {}, "sha512-BUFj1aMubgib37I3v4q78fYo63Po7t4HUPTpQ6/QE6yK6cIQrP+W43FYToeTEyg5m2Y7eFUtijUuAv/PDlWuag=="], + + "file-stream-rotator": ["file-stream-rotator@0.6.1", "", { "dependencies": { "moment": "^2.29.1" } }, "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ=="], + + "find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + + "fn.name": ["fn.name@1.1.0", "", {}, "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="], + + "fontkit": ["fontkit@2.0.4", "", { "dependencies": { "@swc/helpers": "^0.5.12", "brotli": "^1.3.2", "clone": "^2.1.2", "dfa": "^1.2.0", "fast-deep-equal": "^3.1.3", "restructure": "^3.0.0", "tiny-inflate": "^1.0.3", "unicode-properties": "^1.4.0", "unicode-trie": "^2.0.0" } }, "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g=="], + + "formsnap": ["formsnap@2.0.1", "", { "dependencies": { "svelte-toolbelt": "^0.5.0" }, "peerDependencies": { "svelte": "^5.0.0", "sveltekit-superforms": "^2.19.0" } }, "sha512-iJSe4YKd/W6WhLwKDVJU9FQeaJRpEFuolhju7ZXlRpUVyDdqFdMP8AUBICgnVvQPyP41IPAlBa/v0Eo35iE6wQ=="], + + "frac": ["frac@1.1.2", "", {}, "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="], + + "hono": ["hono@4.12.3", "", {}, "sha512-SFsVSjp8sj5UumXOOFlkZOG6XS9SJDKw0TbwFeV+AJ8xlST8kxK5Z/5EYa111UY8732lK2S/xB653ceuaoGwpg=="], + + "html-to-text": ["html-to-text@9.0.5", "", { "dependencies": { "@selderee/plugin-htmlparser2": "^0.11.0", "deepmerge": "^4.3.1", "dom-serializer": "^2.0.0", "htmlparser2": "^8.0.2", "selderee": "^0.11.0" } }, "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg=="], + + "htmlparser2": ["htmlparser2@8.0.2", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1", "entities": "^4.4.0" } }, "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA=="], + + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "imapflow": ["imapflow@1.2.1", "", { "dependencies": { "@zone-eu/mailsplit": "5.4.8", "encoding-japanese": "2.2.0", "iconv-lite": "0.7.0", "libbase64": "1.3.0", "libmime": "5.3.7", "libqp": "2.1.1", "nodemailer": "7.0.11", "pino": "10.1.0", "socks": "2.8.7" } }, "sha512-zoi2hYAtAw/ZRaSZzq5Fu0vxJ8LIGi444YBNN5VJFR0jTi8Y2etwMGO3yacea9XT9qvVSe6FceGtGGTxeqrvpQ=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], + + "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], + + "ioredis": ["ioredis@5.8.2", "", { "dependencies": { "@ioredis/commands": "1.4.0", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q=="], + + "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="], + + "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + + "joi": ["joi@17.13.3", "", { "dependencies": { "@hapi/hoek": "^9.3.0", "@hapi/topo": "^5.1.0", "@sideway/address": "^4.1.5", "@sideway/formula": "^3.0.1", "@sideway/pinpoint": "^2.0.0" } }, "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA=="], + + "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], + + "jpeg-exif": ["jpeg-exif@1.1.4", "", {}, "sha512-a+bKEcCjtuW5WTdgeXFzswSrdqi0jk4XlEtZlx5A94wCoBpFjfFTbo/Tra5SpNCl/YFZPvcV1dJc+TAYeg6ROQ=="], + + "json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="], + + "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], + + "kuler": ["kuler@2.0.0", "", {}, "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="], + + "kysely": ["kysely@0.28.9", "", {}, "sha512-3BeXMoiOhpOwu62CiVpO6lxfq4eS6KMYfQdMsN/2kUCRNuF2YiEr7u0HLHaQU+O4Xu8YXE3bHVkwaQ85i72EuA=="], + + "layerchart": ["layerchart@2.0.0-next.43", "", { "dependencies": { "@dagrejs/dagre": "^1.1.5", "@layerstack/svelte-actions": "1.0.1-next.14", "@layerstack/svelte-state": "0.1.0-next.19", "@layerstack/tailwind": "2.0.0-next.17", "@layerstack/utils": "2.0.0-next.14", "d3-array": "^3.2.4", "d3-color": "^3.1.0", "d3-delaunay": "^6.0.4", "d3-dsv": "^3.0.1", "d3-force": "^3.0.0", "d3-geo": "^3.1.1", "d3-geo-voronoi": "^2.1.0", "d3-hierarchy": "^3.1.2", "d3-interpolate": "^3.0.1", "d3-interpolate-path": "^2.3.0", "d3-path": "^3.1.0", "d3-quadtree": "^3.0.1", "d3-random": "^3.0.1", "d3-sankey": "^0.12.3", "d3-scale": "^4.0.2", "d3-scale-chromatic": "^3.1.0", "d3-shape": "^3.2.0", "d3-tile": "^1.0.0", "d3-time": "^3.1.0", "lodash-es": "^4.17.21", "memoize": "^10.1.0", "runed": "^0.31.1" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-1Ywm38NdzHWKwgaAHq3EcqshIgsq+pylntSnVWAVazXUk/NsxPcxdpR3tMt3ySjWV0ZPBBgLs78sdVf7FTgd+g=="], + + "leac": ["leac@0.6.0", "", {}, "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg=="], + + "libbase64": ["libbase64@1.3.0", "", {}, "sha512-GgOXd0Eo6phYgh0DJtjQ2tO8dc0IVINtZJeARPeiIJqge+HdsWSuaDTe8ztQ7j/cONByDZ3zeB325AHiv5O0dg=="], + + "libmime": ["libmime@5.3.7", "", { "dependencies": { "encoding-japanese": "2.2.0", "iconv-lite": "0.6.3", "libbase64": "1.3.0", "libqp": "2.1.1" } }, "sha512-FlDb3Wtha8P01kTL3P9M+ZDNDWPKPmKHWaU/cG/lg5pfuAwdflVpZE+wm9m7pKmC5ww6s+zTxBKS1p6yl3KpSw=="], + + "libphonenumber-js": ["libphonenumber-js@1.12.33", "", {}, "sha512-r9kw4OA6oDO4dPXkOrXTkArQAafIKAU71hChInV4FxZ69dxCfbwQGDPzqR5/vea94wU705/3AZroEbSoeVWrQw=="], + + "libqp": ["libqp@2.1.1", "", {}, "sha512-0Wd+GPz1O134cP62YU2GTOPNA7Qgl09XwCqM5zpBv87ERCXdfDtyKXvV7c9U22yWJh44QZqBocFnXN11K96qow=="], + + "lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], + + "linebreak": ["linebreak@1.1.0", "", { "dependencies": { "base64-js": "0.0.8", "unicode-trie": "^2.0.0" } }, "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ=="], + + "linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="], + + "local-pkg": ["local-pkg@1.1.2", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A=="], + + "locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="], + + "locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], + + "lodash-es": ["lodash-es@4.17.22", "", {}, "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q=="], + + "lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="], + + "lodash.isarguments": ["lodash.isarguments@3.1.0", "", {}, "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="], + + "logform": ["logform@2.7.0", "", { "dependencies": { "@colors/colors": "1.6.0", "@types/triple-beam": "^1.3.2", "fecha": "^4.2.0", "ms": "^2.1.1", "safe-stable-stringify": "^2.3.1", "triple-beam": "^1.3.0" } }, "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ=="], + + "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "mailparser": ["mailparser@3.9.1", "", { "dependencies": { "@zone-eu/mailsplit": "5.4.8", "encoding-japanese": "2.2.0", "he": "1.2.0", "html-to-text": "9.0.5", "iconv-lite": "0.7.0", "libmime": "5.3.7", "linkify-it": "5.0.0", "nodemailer": "7.0.11", "punycode.js": "2.3.1", "tlds": "1.261.0" } }, "sha512-6vHZcco3fWsDMkf4Vz9iAfxvwrKNGbHx0dV1RKVphQ/zaNY34Buc7D37LSa09jeSeybWzYcTPjhiZFxzVRJedA=="], + + "marked": ["marked@17.0.1", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg=="], + + "memoize": ["memoize@10.2.0", "", { "dependencies": { "mimic-function": "^5.0.1" } }, "sha512-DeC6b7QBrZsRs3Y02A6A7lQyzFbsQbqgjI6UW0GigGWV+u1s25TycMr0XHZE4cJce7rY/vyw2ctMQqfDkIhUEA=="], + + "memoize-weak": ["memoize-weak@1.0.2", "", {}, "sha512-gj39xkrjEw7nCn4nJ1M5ms6+MyMlyiGmttzsqAUsAKn6bYKwuTHh/AO3cKPF8IBrTIYTxb0wWXFs3E//Y8VoWQ=="], + + "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], + + "mini-svg-data-uri": ["mini-svg-data-uri@1.4.4", "", { "bin": { "mini-svg-data-uri": "cli.js" } }, "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg=="], + + "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="], + + "mode-watcher": ["mode-watcher@1.1.0", "", { "dependencies": { "runed": "^0.25.0", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.27.0" } }, "sha512-mUT9RRGPDYenk59qJauN1rhsIMKBmWA3xMF+uRwE8MW/tjhaDSCCARqkSuDTq8vr4/2KcAxIGVjACxTjdk5C3g=="], + + "moment": ["moment@2.30.1", "", {}, "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how=="], + + "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], + + "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], + + "nanostores": ["nanostores@1.1.0", "", {}, "sha512-yJBmDJr18xy47dbNVlHcgdPrulSn1nhSE6Ns9vTG+Nx9VPT6iV1MD6aQFp/t52zpf82FhLLTXAXr30NuCnxvwA=="], + + "neverthrow": ["neverthrow@8.2.0", "", { "optionalDependencies": { "@rollup/rollup-linux-x64-gnu": "^4.24.0" } }, "sha512-kOCT/1MCPAxY5iUV3wytNFUMUolzuwd/VF/1KCx7kf6CutrOsTie+84zTGTpgQycjvfLdBBdvBvFLqFD2c0wkQ=="], + + "next": ["next@15.5.9", "", { "dependencies": { "@next/env": "15.5.9", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.7", "@next/swc-darwin-x64": "15.5.7", "@next/swc-linux-arm64-gnu": "15.5.7", "@next/swc-linux-arm64-musl": "15.5.7", "@next/swc-linux-x64-gnu": "15.5.7", "@next/swc-linux-x64-musl": "15.5.7", "@next/swc-win32-arm64-msvc": "15.5.7", "@next/swc-win32-x64-msvc": "15.5.7", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg=="], + + "node-addon-api": ["node-addon-api@8.5.0", "", {}, "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A=="], + + "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="], + + "nodemailer": ["nodemailer@7.0.11", "", {}, "sha512-gnXhNRE0FNhD7wPSCGhdNh46Hs6nm+uTyg+Kq0cZukNQiYdnCsoQjodNP9BQVG9XrcK/v6/MgpAPBUFyzh9pvw=="], + + "normalize-url": ["normalize-url@8.1.0", "", {}, "sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w=="], + + "object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="], + + "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], + + "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], + + "one-time": ["one-time@1.0.0", "", { "dependencies": { "fn.name": "1.x.x" } }, "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g=="], + + "otplib": ["otplib@12.0.1", "", { "dependencies": { "@otplib/core": "^12.0.1", "@otplib/preset-default": "^12.0.1", "@otplib/preset-v11": "^12.0.1" } }, "sha512-xDGvUOQjop7RDgxTQ+o4pOol0/3xSZzawTiPKRrHnQWAy0WjhNs/5HdIDJCrqC4MBynmjXgULc6YfioaxZeFgg=="], + + "p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + + "p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + + "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], + + "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], + + "pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="], + + "paneforge": ["paneforge@1.0.2", "", { "dependencies": { "runed": "^0.23.4", "svelte-toolbelt": "^0.9.2" }, "peerDependencies": { "svelte": "^5.29.0" } }, "sha512-KzmIXQH1wCfwZ4RsMohD/IUtEjVhteR+c+ulb/CHYJHX8SuDXoJmChtsc/Xs5Wl8NHS4L5Q7cxL8MG40gSU1bA=="], + + "parseley": ["parseley@0.12.1", "", { "dependencies": { "leac": "^0.6.0", "peberminta": "^0.9.0" } }, "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "pdfkit": ["pdfkit@0.17.2", "", { "dependencies": { "crypto-js": "^4.2.0", "fontkit": "^2.0.4", "jpeg-exif": "^1.1.4", "linebreak": "^1.1.0", "png-js": "^1.0.0" } }, "sha512-UnwF5fXy08f0dnp4jchFYAROKMNTaPqb/xgR8GtCzIcqoTnbOqtp3bwKvO4688oHI6vzEEs8Q6vqqEnC5IUELw=="], + + "peberminta": ["peberminta@0.9.0", "", {}, "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ=="], + + "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], + + "pg-protocol": ["pg-protocol@1.10.3", "", {}, "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ=="], + + "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "pino": ["pino@10.1.0", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w=="], + + "pino-abstract-transport": ["pino-abstract-transport@2.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw=="], + + "pino-std-serializers": ["pino-std-serializers@7.0.0", "", {}, "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA=="], + + "pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], + + "png-js": ["png-js@1.0.0", "", {}, "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g=="], + + "pngjs": ["pngjs@5.0.0", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="], + + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + + "postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="], + + "postgres": ["postgres@3.4.8", "", {}, "sha512-d+JFcLM17njZaOLkv6SCev7uoLaBtfK86vMUXhW1Z4glPWh4jozno9APvW/XKFJ3CCxVoC7OL38BqRydtu5nGg=="], + + "postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], + + "postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="], + + "postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="], + + "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], + + "prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="], + + "prettier-plugin-sort-imports": ["prettier-plugin-sort-imports@1.8.11", "", { "dependencies": { "prettier": "^3.1.1" }, "peerDependencies": { "typescript": ">4.0.0" } }, "sha512-ApmZkEmNh2Fi6TAcYNgUR15rsP1+omX43+8neY+MXfNZ7JQwrqSdjzKhLUYTtaLo52aaC9gCs+lJaYSU8oSJJA=="], + + "prettier-plugin-svelte": ["prettier-plugin-svelte@3.5.0", "", { "peerDependencies": { "prettier": "^3.0.0", "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" } }, "sha512-2lLO/7EupnjO/95t+XZesXs8Bf3nYLIDfCo270h5QWbj/vjLqmrQ1LiRk9LPggxSDsnVYfehamZNf+rgQYApZg=="], + + "prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.7.2", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-hermes": "*", "@prettier/plugin-oxc": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-hermes", "@prettier/plugin-oxc", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-svelte"] }, "sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA=="], + + "process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="], + + "property-expr": ["property-expr@2.0.6", "", {}, "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA=="], + + "punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="], + + "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], + + "qrcode": ["qrcode@1.5.4", "", { "dependencies": { "dijkstrajs": "^1.0.1", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, "bin": { "qrcode": "bin/qrcode" } }, "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg=="], + + "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], + + "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], + + "react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="], + + "react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + + "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="], + + "redis-errors": ["redis-errors@1.2.0", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="], + + "redis-parser": ["redis-parser@3.0.0", "", { "dependencies": { "redis-errors": "^1.0.0" } }, "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A=="], + + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="], + + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + + "restructure": ["restructure@3.0.2", "", {}, "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw=="], + + "robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="], + + "rolldown": ["rolldown@1.0.0-beta.9-commit.d91dfb5", "", { "dependencies": { "@oxc-project/runtime": "0.71.0", "@oxc-project/types": "0.71.0", "@rolldown/pluginutils": "1.0.0-beta.9-commit.d91dfb5", "ansis": "^4.0.0" }, "optionalDependencies": { "@rolldown/binding-darwin-arm64": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-darwin-x64": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-freebsd-x64": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.9-commit.d91dfb5" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-FHkj6gGEiEgmAXQchglofvUUdwj2Oiw603Rs+zgFAnn9Cb7T7z3fiaEc0DbN3ja4wYkW6sF2rzMEtC1V4BGx/g=="], + + "rollup": ["rollup@4.54.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.54.0", "@rollup/rollup-android-arm64": "4.54.0", "@rollup/rollup-darwin-arm64": "4.54.0", "@rollup/rollup-darwin-x64": "4.54.0", "@rollup/rollup-freebsd-arm64": "4.54.0", "@rollup/rollup-freebsd-x64": "4.54.0", "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", "@rollup/rollup-linux-arm-musleabihf": "4.54.0", "@rollup/rollup-linux-arm64-gnu": "4.54.0", "@rollup/rollup-linux-arm64-musl": "4.54.0", "@rollup/rollup-linux-loong64-gnu": "4.54.0", "@rollup/rollup-linux-ppc64-gnu": "4.54.0", "@rollup/rollup-linux-riscv64-gnu": "4.54.0", "@rollup/rollup-linux-riscv64-musl": "4.54.0", "@rollup/rollup-linux-s390x-gnu": "4.54.0", "@rollup/rollup-linux-x64-gnu": "4.54.0", "@rollup/rollup-linux-x64-musl": "4.54.0", "@rollup/rollup-openharmony-arm64": "4.54.0", "@rollup/rollup-win32-arm64-msvc": "4.54.0", "@rollup/rollup-win32-ia32-msvc": "4.54.0", "@rollup/rollup-win32-x64-gnu": "4.54.0", "@rollup/rollup-win32-x64-msvc": "4.54.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw=="], + + "rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="], + + "runed": ["runed@0.35.1", "", { "dependencies": { "dequal": "^2.0.3", "esm-env": "^1.0.0", "lz-string": "^1.5.0" }, "peerDependencies": { "@sveltejs/kit": "^2.21.0", "svelte": "^5.7.0" }, "optionalPeers": ["@sveltejs/kit"] }, "sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q=="], + + "rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="], + + "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], + + "selderee": ["selderee@0.11.0", "", { "dependencies": { "parseley": "^0.12.0" } }, "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA=="], + + "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="], + + "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], + + "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], + + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + + "sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="], + + "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + + "socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="], + + "sonic-boom": ["sonic-boom@4.2.0", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + + "ssf": ["ssf@0.11.2", "", { "dependencies": { "frac": "~1.1.2" } }, "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g=="], + + "stack-trace": ["stack-trace@0.0.10", "", {}, "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg=="], + + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="], + + "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], + + "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], + + "superstruct": ["superstruct@2.0.2", "", {}, "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A=="], + + "svelte": ["svelte@5.46.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.5.0", "esm-env": "^1.2.1", "esrap": "^2.2.1", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-ZhLtvroYxUxr+HQJfMZEDRsGsmU46x12RvAv/zi9584f5KOX7bUrEbhPJ7cKFmUvZTJXi/CFZUYwDC6M1FigPw=="], + + "svelte-adapter-bun": ["svelte-adapter-bun@1.0.1", "", { "dependencies": { "rolldown": "^1.0.0-beta.38" }, "peerDependencies": { "@sveltejs/kit": "^2.4.0", "typescript": "^5" } }, "sha512-tNOvfm8BGgG+rmEA7hkmqtq07v7zoo4skLQc+hIoQ79J+1fkEMpJEA2RzCIe3aPc8JdrsMJkv3mpiZPMsgahjA=="], + + "svelte-check": ["svelte-check@4.3.5", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-e4VWZETyXaKGhpkxOXP+B/d0Fp/zKViZoJmneZWe/05Y2aqSKj3YN2nLfYPJBQ87WEiY4BQCQ9hWGu9mPT1a1Q=="], + + "svelte-sonner": ["svelte-sonner@1.0.7", "", { "dependencies": { "runed": "^0.28.0" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-1EUFYmd7q/xfs2qCHwJzGPh9n5VJ3X6QjBN10fof2vxgy8fYE7kVfZ7uGnd7i6fQaWIr5KvXcwYXE/cmTEjk5A=="], + + "svelte-toolbelt": ["svelte-toolbelt@0.10.6", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.35.1", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.30.2" } }, "sha512-YWuX+RE+CnWYx09yseAe4ZVMM7e7GRFZM6OYWpBKOb++s+SQ8RBIMMe+Bs/CznBMc0QPLjr+vDBxTAkozXsFXQ=="], + + "sveltekit-superforms": ["sveltekit-superforms@2.29.1", "", { "dependencies": { "devalue": "^5.6.1", "memoize-weak": "^1.0.2", "ts-deepmerge": "^7.0.3" }, "optionalDependencies": { "@exodus/schemasafe": "^1.3.0", "@typeschema/class-validator": "^0.3.0", "@valibot/to-json-schema": "^1.5.0", "@vinejs/vine": "^3.0.1", "arktype": "^2.1.29", "class-validator": "^0.14.3", "effect": "^3.19.12", "joi": "^17.13.3", "json-schema-to-ts": "^3.1.1", "superstruct": "^2.0.2", "typebox": "^1.0.62", "valibot": "^1.2.0", "yup": "^1.7.1", "zod": "^4.1.13", "zod-v3-to-json-schema": "^4.0.0" }, "peerDependencies": { "@sveltejs/kit": "1.x || 2.x", "svelte": "3.x || 4.x || >=5.0.0-next.51" } }, "sha512-9Cv1beOVPgm8rb8NZBqLdlZ9cBqRBTk0+6/oHn7DWvHQoAFie1EPjh1e4NHO3Qouv1Zq9QTGrZNDbYcetkuOVw=="], + + "tabbable": ["tabbable@6.3.0", "", {}, "sha512-EIHvdY5bPLuWForiR/AN2Bxngzpuwn1is4asboytXtpTgsArc+WmSJKVLlhdh71u7jFcryDqB2A8lQvj78MkyQ=="], + + "tailwind-merge": ["tailwind-merge@3.4.0", "", {}, "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g=="], + + "tailwind-variants": ["tailwind-variants@3.2.2", "", { "peerDependencies": { "tailwind-merge": ">=3.0.0", "tailwindcss": "*" }, "optionalPeers": ["tailwind-merge"] }, "sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg=="], + + "tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="], + + "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], + + "text-hex": ["text-hex@1.0.0", "", {}, "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="], + + "thirty-two": ["thirty-two@1.0.2", "", {}, "sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA=="], + + "thread-stream": ["thread-stream@3.1.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A=="], + + "tiny-case": ["tiny-case@1.0.3", "", {}, "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q=="], + + "tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="], + + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "tinyrainbow": ["tinyrainbow@3.0.3", "", {}, "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q=="], + + "tlds": ["tlds@1.261.0", "", { "bin": { "tlds": "bin.js" } }, "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA=="], + + "tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="], + + "toposort": ["toposort@2.0.2", "", {}, "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg=="], + + "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="], + + "triple-beam": ["triple-beam@1.4.1", "", {}, "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg=="], + + "ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="], + + "ts-deepmerge": ["ts-deepmerge@7.0.3", "", {}, "sha512-Du/ZW2RfwV/D4cmA5rXafYjBQVuvu4qGiEEla4EmEHVHgRdx68Gftx7i66jn2bzHPwSVZY36Ae6OuDn9el4ZKA=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "turbo": ["turbo@2.7.0", "", { "optionalDependencies": { "turbo-darwin-64": "2.7.0", "turbo-darwin-arm64": "2.7.0", "turbo-linux-64": "2.7.0", "turbo-linux-arm64": "2.7.0", "turbo-windows-64": "2.7.0", "turbo-windows-arm64": "2.7.0" }, "bin": { "turbo": "bin/turbo" } }, "sha512-1dUGwi6cSSVZts1BwJa/Gh7w5dPNNGsNWZEAuRKxXWME44hTKWpQZrgiPnqMc5jJJOovzPK5N6tL+PHYRYL5Wg=="], + + "turbo-darwin-64": ["turbo-darwin-64@2.7.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-gwqL7cJOSYrV/jNmhXM8a2uzSFn7GcUASOuen6OgmUsafUj9SSWcgXZ/q0w9hRoL917hpidkdI//UpbxbZbwwg=="], + + "turbo-darwin-arm64": ["turbo-darwin-arm64@2.7.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-f3F5DYOnfE6lR6v/rSld7QGZgartKsnlIYY7jcF/AA7Wz27za9XjxMHzb+3i4pvRhAkryFgf2TNq7eCFrzyTpg=="], + + "turbo-linux-64": ["turbo-linux-64@2.7.0", "", { "os": "linux", "cpu": "x64" }, "sha512-KsC+UuKlhjCL+lom10/IYoxUsdhJOsuEki72YSr7WGYUSRihcdJQnaUyIDTlm0nPOb+gVihVNBuVP4KsNg1UnA=="], + + "turbo-linux-arm64": ["turbo-linux-arm64@2.7.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-1tjIYULeJtpmE/ovoI9qPBFJCtUEM7mYfeIMOIs4bXR6t/8u+rHPwr3j+vRHcXanIc42V1n3Pz52VqmJtIAviw=="], + + "turbo-windows-64": ["turbo-windows-64@2.7.0", "", { "os": "win32", "cpu": "x64" }, "sha512-KThkAeax46XiH+qICCQm7R8V2pPdeTTP7ArCSRrSLqnlO75ftNm8Ljx4VAllwIZkILrq/GDM8PlyhZdPeUdDxQ=="], + + "turbo-windows-arm64": ["turbo-windows-arm64@2.7.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-kzI6rsQ3Ejs+CkM9HEEP3Z4h5YMCRxwIlQXFQmgXSG3BIgorCkRF2Xr7iQ2i9AGwY/6jbiAYeJbvi3yCp+noFw=="], + + "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], + + "type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="], + + "typebox": ["typebox@1.0.65", "", {}, "sha512-3WaZ4QmfAxmelhi0dwusYDoZ+DLDoVrsc3aORzgtk1I8JfIf4wn+F8i1TtrnU2jJKM/hZgjJGfzXrwS4B31zZw=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="], + + "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], + + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "unicode-properties": ["unicode-properties@1.4.1", "", { "dependencies": { "base64-js": "^1.3.0", "unicode-trie": "^2.0.0" } }, "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg=="], + + "unicode-trie": ["unicode-trie@2.0.0", "", { "dependencies": { "pako": "^0.2.5", "tiny-inflate": "^1.0.0" } }, "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ=="], + + "unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="], + + "unplugin-icons": ["unplugin-icons@23.0.1", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/utils": "^3.1.0", "local-pkg": "^1.1.2", "obug": "^2.1.1", "unplugin": "^2.3.11" }, "peerDependencies": { "@svgr/core": ">=7.0.0", "@svgx/core": "^1.0.1", "@vue/compiler-sfc": "^3.0.2", "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0" }, "optionalPeers": ["@svgr/core", "@svgx/core", "@vue/compiler-sfc", "svelte"] }, "sha512-rv0XEJepajKzDLvRUWASM8K+8+/CCfZn2jtogXqg6RIp7kpatRc/aFrVJn8ANQA09e++lPEEv9yX8cC9enc+QQ=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], + + "valibot": ["valibot@1.2.0", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg=="], + + "validator": ["validator@13.15.26", "", {}, "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA=="], + + "vaul-svelte": ["vaul-svelte@1.0.0-next.7", "", { "dependencies": { "runed": "^0.23.2", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-7zN7Bi3dFQixvvbUJY9uGDe7Ws/dGZeBQR2pXdXmzQiakjrxBvWo0QrmsX3HK+VH+SZOltz378cmgmCS9f9rSg=="], + + "vite": ["vite@7.3.0", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg=="], + + "vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="], + + "vitest": ["vitest@4.0.16", "", { "dependencies": { "@vitest/expect": "4.0.16", "@vitest/mocker": "4.0.16", "@vitest/pretty-format": "4.0.16", "@vitest/runner": "4.0.16", "@vitest/snapshot": "4.0.16", "@vitest/spy": "4.0.16", "@vitest/utils": "4.0.16", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.16", "@vitest/browser-preview": "4.0.16", "@vitest/browser-webdriverio": "4.0.16", "@vitest/ui": "4.0.16", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q=="], + + "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], + + "which-module": ["which-module@2.0.1", "", {}, "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="], + + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + + "winston": ["winston@3.19.0", "", { "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.8", "async": "^3.2.3", "is-stream": "^2.0.0", "logform": "^2.7.0", "one-time": "^1.0.0", "readable-stream": "^3.4.0", "safe-stable-stringify": "^2.3.1", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", "winston-transport": "^4.9.0" } }, "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA=="], + + "winston-daily-rotate-file": ["winston-daily-rotate-file@5.0.0", "", { "dependencies": { "file-stream-rotator": "^0.6.1", "object-hash": "^3.0.0", "triple-beam": "^1.4.1", "winston-transport": "^4.7.0" }, "peerDependencies": { "winston": "^3" } }, "sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw=="], + + "winston-transport": ["winston-transport@4.9.0", "", { "dependencies": { "logform": "^2.7.0", "readable-stream": "^3.6.2", "triple-beam": "^1.3.0" } }, "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A=="], + + "wmf": ["wmf@1.0.2", "", {}, "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw=="], + + "word": ["word@0.3.0", "", {}, "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA=="], + + "wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], + + "xlsx": ["xlsx@0.18.5", "", { "dependencies": { "adler-32": "~1.3.0", "cfb": "~1.2.1", "codepage": "~1.15.0", "crc-32": "~1.2.1", "ssf": "~0.11.2", "wmf": "~1.0.1", "word": "~0.3.0" }, "bin": { "xlsx": "bin/xlsx.njs" } }, "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ=="], + + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + + "y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="], + + "yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="], + + "yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="], + + "yup": ["yup@1.7.1", "", { "dependencies": { "property-expr": "^2.0.5", "tiny-case": "^1.0.3", "toposort": "^2.0.2", "type-fest": "^2.19.0" } }, "sha512-GKHFX2nXul2/4Dtfxhozv701jLQHdf6J34YDh2cEkpqoo8le5Mg6/LrdseVLrFarmFygZTlfIhHx/QKfb/QWXw=="], + + "zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="], + + "zod": ["zod@4.2.1", "", {}, "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw=="], + + "zod-v3-to-json-schema": ["zod-v3-to-json-schema@4.0.0", "", { "peerDependencies": { "zod": "^3.25 || ^4.0.14" } }, "sha512-KixLrhX/uPmRFnDgsZrzrk4x5SSJA+PmaE5adbfID9+3KPJcdxqRobaHU397EfWBqfQircrjKqvEqZ/mW5QH6w=="], + + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], + + "@neondatabase/serverless/@types/node": ["@types/node@22.19.3", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA=="], + + "@pkg/settings/dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.0", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "brotli/base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "d3-sankey/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="], + + "d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="], + + "drizzle-kit/esbuild": ["esbuild@0.19.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="], + + "formsnap/svelte-toolbelt": ["svelte-toolbelt@0.5.0", "", { "dependencies": { "clsx": "^2.1.1", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0-next.126" } }, "sha512-t3tenZcnfQoIeRuQf/jBU7bvTeT3TGkcEE+1EUr5orp0lR7NEpprflpuie3x9Dn0W9nOKqs3HwKGJeeN5Ok1sQ=="], + + "imapflow/iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="], + + "layerchart/runed": ["runed@0.31.1", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-v3czcTnO+EJjiPvD4dwIqfTdHLZ8oH0zJheKqAHh9QMViY7Qb29UlAMRpX7ZtHh7AFqV60KmfxaJ9QMy+L1igQ=="], + + "mailparser/iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="], + + "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + + "mode-watcher/runed": ["runed@0.25.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-7+ma4AG9FT2sWQEA0Egf6mb7PBT2vHyuHail1ie8ropfSjvZGtEAx8YTmUjv/APCsdRRxEVvArNjALk9zFSOrg=="], + + "mode-watcher/svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="], + + "next/@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], + + "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], + + "paneforge/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="], + + "paneforge/svelte-toolbelt": ["svelte-toolbelt@0.9.3", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.29.0", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.30.2" } }, "sha512-HCSWxCtVmv+c6g1ACb8LTwHVbDqLKJvHpo6J8TaqwUme2hj9ATJCpjCPNISR1OCq2Q4U1KT41if9ON0isINQZw=="], + + "postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "svelte-sonner/runed": ["runed@0.28.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ=="], + + "unicode-properties/base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "vaul-svelte/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="], + + "vaul-svelte/svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="], + + "yargs-parser/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], + + "@neondatabase/serverless/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "d3-sankey/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], + + "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="], + + "drizzle-kit/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="], + + "drizzle-kit/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.19.12", "", { "os": "android", "cpu": "arm" }, "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w=="], + + "drizzle-kit/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.19.12", "", { "os": "android", "cpu": "arm64" }, "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA=="], + + "drizzle-kit/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.19.12", "", { "os": "android", "cpu": "x64" }, "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew=="], + + "drizzle-kit/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.19.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g=="], + + "drizzle-kit/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.19.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A=="], + + "drizzle-kit/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.19.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA=="], + + "drizzle-kit/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.19.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg=="], + + "drizzle-kit/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.19.12", "", { "os": "linux", "cpu": "arm" }, "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w=="], + + "drizzle-kit/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.19.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA=="], + + "drizzle-kit/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.19.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA=="], + + "drizzle-kit/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA=="], + + "drizzle-kit/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w=="], + + "drizzle-kit/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.19.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg=="], + + "drizzle-kit/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg=="], + + "drizzle-kit/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.19.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg=="], + + "drizzle-kit/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.19.12", "", { "os": "linux", "cpu": "x64" }, "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg=="], + + "drizzle-kit/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.19.12", "", { "os": "none", "cpu": "x64" }, "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA=="], + + "drizzle-kit/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.19.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw=="], + + "drizzle-kit/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.19.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA=="], + + "drizzle-kit/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.19.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A=="], + + "drizzle-kit/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.19.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ=="], + + "drizzle-kit/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.19.12", "", { "os": "win32", "cpu": "x64" }, "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA=="], + + "mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + + "mode-watcher/svelte-toolbelt/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="], + + "next/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "paneforge/svelte-toolbelt/runed": ["runed@0.29.2", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-0cq6cA6sYGZwl/FvVqjx9YN+1xEBu9sDDyuWdDW1yWX7JF2wmvmVKfH+hVCZs+csW+P3ARH92MjI3H9QTagOQA=="], + } +} diff --git a/dev/README.md b/dev/README.md new file mode 100644 index 0000000..23b0c33 --- /dev/null +++ b/dev/README.md @@ -0,0 +1,42 @@ +# Dev + +Self-contained local development stack. Spin up all shared infrastructure on a per-project basis. + +## Services + +| Service | Description | Port(s) | +| ------------------ | ---------------------------------------- | -------------- | +| **PostgreSQL** | Primary relational database | `5432` | +| **Valkey** | Redis-compatible cache / message broker | `6379` | +| **SigNoz** | Observability UI (traces, metrics, logs) | `8080` | +| **OTel Collector** | OpenTelemetry ingest (gRPC / HTTP) | `4317`, `4318` | +| **ClickHouse** | Telemetry storage backend for SigNoz | — | + +## Run + +```sh +cd dev +docker compose -f docker-compose.dev.yaml up -d +``` + +## Stop + +```sh +docker compose -f docker-compose.dev.yaml down +``` + +To also remove all persisted data volumes: + +```sh +docker compose -f docker-compose.dev.yaml down -v +``` + +## Connection strings + +| Resource | Default value | +| ---------- | --------------------------------------------------------- | +| PostgreSQL | `postgresql://postgres:postgres@localhost:5432/primarydb` | +| Valkey | `redis://localhost:6379` | +| SigNoz UI | `http://localhost:8080` | +| OTLP gRPC | `localhost:4317` | +| OTLP HTTP | `localhost:4318` | diff --git a/dev/clickhouse-cluster.xml b/dev/clickhouse-cluster.xml new file mode 100644 index 0000000..8b475ff --- /dev/null +++ b/dev/clickhouse-cluster.xml @@ -0,0 +1,75 @@ + + + + + + zookeeper-1 + 2181 + + + + + + + + + + + + + + + + clickhouse + 9000 + + + + + + + + diff --git a/dev/clickhouse-config.xml b/dev/clickhouse-config.xml new file mode 100644 index 0000000..1965ac3 --- /dev/null +++ b/dev/clickhouse-config.xml @@ -0,0 +1,1142 @@ + + + + + + information + + json + + /var/log/clickhouse-server/clickhouse-server.log + /var/log/clickhouse-server/clickhouse-server.err.log + + 1000M + 10 + + + + + + + + + + + + + + + + + + 8123 + + + 9000 + + + 9004 + + + 9005 + + + + + + + + + + + + 9009 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4096 + + + 3 + + + + + false + + + /path/to/ssl_cert_file + /path/to/ssl_key_file + + + false + + + /path/to/ssl_ca_cert_file + + + none + + + 0 + + + -1 + -1 + + + false + + + + + + + + + + + none + true + true + sslv2,sslv3 + true + + + + true + true + sslv2,sslv3 + true + + + + RejectCertificateHandler + + + + + + + + + 100 + + + 0 + + + + 10000 + + + + + + 0.9 + + + 4194304 + + + 0 + + + + + + 8589934592 + + + 5368709120 + + + + 1000 + + + 134217728 + + + 10000 + + + /var/lib/clickhouse/ + + + /var/lib/clickhouse/tmp/ + + + + ` + + + + + + /var/lib/clickhouse/user_files/ + + + + + + + + + + + + + users.xml + + + + /var/lib/clickhouse/access/ + + + + + + + default + + + + + + + + + + + + default + + + + + + + + + true + + + false + + ' | sed -e 's|.*>\(.*\)<.*|\1|') + wget https://github.com/ClickHouse/clickhouse-jdbc-bridge/releases/download/v$PKG_VER/clickhouse-jdbc-bridge_$PKG_VER-1_all.deb + apt install --no-install-recommends -f ./clickhouse-jdbc-bridge_$PKG_VER-1_all.deb + clickhouse-jdbc-bridge & + + * [CentOS/RHEL] + export MVN_URL=https://repo1.maven.org/maven2/ru/yandex/clickhouse/clickhouse-jdbc-bridge + export PKG_VER=$(curl -sL $MVN_URL/maven-metadata.xml | grep '' | sed -e 's|.*>\(.*\)<.*|\1|') + wget https://github.com/ClickHouse/clickhouse-jdbc-bridge/releases/download/v$PKG_VER/clickhouse-jdbc-bridge-$PKG_VER-1.noarch.rpm + yum localinstall -y clickhouse-jdbc-bridge-$PKG_VER-1.noarch.rpm + clickhouse-jdbc-bridge & + + Please refer to https://github.com/ClickHouse/clickhouse-jdbc-bridge#usage for more information. + ]]> + + + + + + + + + + + + + + + 01 + example01-01-1 + + + + + + 3600 + + + + 3600 + + + 60 + + + + + + + + + + /metrics + 9363 + + true + true + true + true + + + + + + system + query_log
    + + toYYYYMM(event_date) + + + + + + 7500 +
    + + + + system + trace_log
    + + toYYYYMM(event_date) + 7500 +
    + + + + system + query_thread_log
    + toYYYYMM(event_date) + 7500 +
    + + + + system + query_views_log
    + toYYYYMM(event_date) + 7500 +
    + + + + system + part_log
    + toYYYYMM(event_date) + 7500 +
    + + + + + + system + metric_log
    + 7500 + 1000 +
    + + + + system + asynchronous_metric_log
    + + 7000 +
    + + + + + + engine MergeTree + partition by toYYYYMM(finish_date) + order by (finish_date, finish_time_us, trace_id) + + system + opentelemetry_span_log
    + 7500 +
    + + + + + system + crash_log
    + + + 1000 +
    + + + + + + + system + processors_profile_log
    + + toYYYYMM(event_date) + 7500 +
    + + + + + + + + + *_dictionary.xml + + + *function.xml + /var/lib/clickhouse/user_scripts/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /clickhouse/task_queue/ddl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + click_cost + any + + 0 + 3600 + + + 86400 + 60 + + + + max + + 0 + 60 + + + 3600 + 300 + + + 86400 + 3600 + + + + + + /var/lib/clickhouse/format_schemas/ + + + + + hide encrypt/decrypt arguments + ((?:aes_)?(?:encrypt|decrypt)(?:_mysql)?)\s*\(\s*(?:'(?:\\'|.)+'|.*?)\s*\) + + \1(???) + + + + + + + + + + false + + false + + + https://6f33034cfe684dd7a3ab9875e57b1c8d@o388870.ingest.sentry.io/5226277 + + + + + + + + + + + 268435456 + true + +
    diff --git a/dev/clickhouse-custom-function.xml b/dev/clickhouse-custom-function.xml new file mode 100644 index 0000000..b2b3f91 --- /dev/null +++ b/dev/clickhouse-custom-function.xml @@ -0,0 +1,21 @@ + + + executable + histogramQuantile + Float64 + + Array(Float64) + buckets + + + Array(Float64) + counts + + + Float64 + quantile + + CSV + ./histogramQuantile + + diff --git a/dev/clickhouse-users.xml b/dev/clickhouse-users.xml new file mode 100644 index 0000000..f185620 --- /dev/null +++ b/dev/clickhouse-users.xml @@ -0,0 +1,123 @@ + + + + + + + + + + 10000000000 + + + random + + + + + 1 + + + + + + + + + + + + + ::/0 + + + + default + + + default + + + + + + + + + + + + + + 3600 + + + 0 + 0 + 0 + 0 + 0 + + + + diff --git a/dev/docker-compose.dev.yaml b/dev/docker-compose.dev.yaml new file mode 100644 index 0000000..3b3a752 --- /dev/null +++ b/dev/docker-compose.dev.yaml @@ -0,0 +1,218 @@ +x-common: &common + networks: + - signoz-net + restart: unless-stopped + logging: + options: + max-size: 50m + max-file: "3" + +x-clickhouse-defaults: &clickhouse-defaults + !!merge <<: *common + image: clickhouse/clickhouse-server:25.5.6 + tty: true + labels: + signoz.io/scrape: "true" + signoz.io/port: "9363" + signoz.io/path: "/metrics" + depends_on: + init-clickhouse: + condition: service_completed_successfully + zookeeper-1: + condition: service_healthy + healthcheck: + test: + - CMD + - wget + - --spider + - -q + - 0.0.0.0:8123/ping + interval: 30s + timeout: 5s + retries: 3 + ulimits: + nproc: 65535 + nofile: + soft: 262144 + hard: 262144 + environment: + - CLICKHOUSE_SKIP_USER_SETUP=1 +x-zookeeper-defaults: &zookeeper-defaults + !!merge <<: *common + image: signoz/zookeeper:3.7.1 + user: root + labels: + signoz.io/scrape: "true" + signoz.io/port: "9141" + signoz.io/path: "/metrics" + healthcheck: + test: + - CMD-SHELL + - curl -s -m 2 http://localhost:8080/commands/ruok | grep error | grep null + interval: 30s + timeout: 5s + retries: 3 +x-db-depend: &db-depend + !!merge <<: *common + depends_on: + clickhouse: + condition: service_healthy +# ====== +# Main +# ====== +services: + valkey: + restart: always + image: valkey/valkey:9.0.3 + networks: + - signoz-net + ports: + - 6379:6379 + volumes: + - dev_valkey_data:/data + postgresql: + restart: always + image: postgres:18.3 + networks: + - signoz-net + ports: + - 5432:5432 + environment: + POSTGRES_DB: primarydb + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + volumes: + - dev_postgresql_data:/var/lib/postgresql + + init-clickhouse: + !!merge <<: *common + image: clickhouse/clickhouse-server:25.5.6 + container_name: signoz-init-clickhouse + command: + - bash + - -c + - | + version="v0.0.1" + node_os=$$(uname -s | tr '[:upper:]' '[:lower:]') + node_arch=$$(uname -m | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) + echo "Fetching histogram-binary for $${node_os}/$${node_arch}" + cd /tmp + wget -O histogram-quantile.tar.gz "https://github.com/SigNoz/signoz/releases/download/histogram-quantile%2F$${version}/histogram-quantile_$${node_os}_$${node_arch}.tar.gz" + tar -xvzf histogram-quantile.tar.gz + mv histogram-quantile /var/lib/clickhouse/user_scripts/histogramQuantile + restart: on-failure + volumes: + - clickhouse-user-scripts:/var/lib/clickhouse/user_scripts/ + zookeeper-1: + !!merge <<: *zookeeper-defaults + container_name: signoz-zookeeper-1 + # ports: + # - "2181:2181" + # - "2888:2888" + # - "3888:3888" + volumes: + - zookeeper-1:/bitnami/zookeeper + environment: + - ZOO_SERVER_ID=1 + - ALLOW_ANONYMOUS_LOGIN=yes + - ZOO_AUTOPURGE_INTERVAL=1 + - ZOO_ENABLE_PROMETHEUS_METRICS=yes + - ZOO_PROMETHEUS_METRICS_PORT_NUMBER=9141 + clickhouse: + !!merge <<: *clickhouse-defaults + container_name: signoz-clickhouse + # ports: + # - "9000:9000" + # - "8123:8123" + # - "9181:9181" + volumes: + - ./clickhouse-config.xml:/etc/clickhouse-server/config.xml + - ./clickhouse-users.xml:/etc/clickhouse-server/users.xml + - ./clickhouse-custom-function.xml:/etc/clickhouse-server/custom-function.xml + - clickhouse-user-scripts:/var/lib/clickhouse/user_scripts/ + - ./clickhouse-cluster.xml:/etc/clickhouse-server/config.d/cluster.xml + - clickhouse:/var/lib/clickhouse/ + # - ./clickhouse-storage.xml:/etc/clickhouse-server/config.d/storage.xml + signoz: + !!merge <<: *db-depend + image: signoz/signoz:${VERSION:-v0.113.0} + container_name: signoz + ports: + - "8080:8080" # signoz port + volumes: + - sqlite:/var/lib/signoz/ + environment: + - SIGNOZ_ALERTMANAGER_PROVIDER=signoz + - SIGNOZ_TELEMETRYSTORE_CLICKHOUSE_DSN=tcp://clickhouse:9000 + - SIGNOZ_SQLSTORE_SQLITE_PATH=/var/lib/signoz/signoz.db + - SIGNOZ_TOKENIZER_JWT_SECRET=secret + healthcheck: + test: + - CMD + - wget + - --spider + - -q + - localhost:8080/api/v1/health + interval: 30s + timeout: 5s + retries: 3 + otel-collector: + !!merge <<: *db-depend + image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.1} + container_name: signoz-otel-collector + entrypoint: + - /bin/sh + command: + - -c + - | + /signoz-otel-collector migrate sync check && + /signoz-otel-collector --config=/etc/otel-collector-config.yaml --manager-config=/etc/manager-config.yaml --copy-path=/var/tmp/collector-config.yaml + volumes: + - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml + - ./otel-collector-opamp-config.yaml:/etc/manager-config.yaml + environment: + - OTEL_RESOURCE_ATTRIBUTES=host.name=signoz-host,os.type=linux + - LOW_CARDINAL_EXCEPTION_GROUPING=false + - SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000 + - SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_CLUSTER=cluster + - SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_REPLICATION=true + - SIGNOZ_OTEL_COLLECTOR_TIMEOUT=10m + ports: + # - "1777:1777" # pprof extension + - "4317:4317" # OTLP gRPC receiver + - "4318:4318" # OTLP HTTP receiver + signoz-telemetrystore-migrator: + !!merge <<: *db-depend + image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.1} + container_name: signoz-telemetrystore-migrator + environment: + - SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000 + - SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_CLUSTER=cluster + - SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_REPLICATION=true + - SIGNOZ_OTEL_COLLECTOR_TIMEOUT=10m + entrypoint: + - /bin/sh + command: + - -c + - | + /signoz-otel-collector migrate bootstrap && + /signoz-otel-collector migrate sync up && + /signoz-otel-collector migrate async up + restart: on-failure +# Peripherals +networks: + signoz-net: + name: signoz-net +volumes: + dev_valkey_data: + name: dev-valkey-data + dev_postgresql_data: + name: dev-postgresql-data + clickhouse: + name: signoz-clickhouse + clickhouse-user-scripts: + name: signoz-clickhouse-user-scripts + sqlite: + name: signoz-sqlite + zookeeper-1: + name: signoz-zookeeper-1 diff --git a/dev/otel-collector-config.yaml b/dev/otel-collector-config.yaml new file mode 100644 index 0000000..88d395b --- /dev/null +++ b/dev/otel-collector-config.yaml @@ -0,0 +1,124 @@ +connectors: + signozmeter: + metrics_flush_interval: 1h + dimensions: + - name: service.name + - name: deployment.environment + - name: host.name +receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + http: + endpoint: 0.0.0.0:4318 + +processors: + batch: + send_batch_size: 10000 + send_batch_max_size: 11000 + timeout: 10s + batch/meter: + send_batch_max_size: 25000 + send_batch_size: 20000 + timeout: 1s + resourcedetection: + # Using OTEL_RESOURCE_ATTRIBUTES envvar, env detector adds custom labels. + detectors: [env, system] + timeout: 2s + signozspanmetrics/delta: + metrics_exporter: signozclickhousemetrics + metrics_flush_interval: 60s + latency_histogram_buckets: + [ + 100us, + 1ms, + 2ms, + 6ms, + 10ms, + 50ms, + 100ms, + 250ms, + 500ms, + 1000ms, + 1400ms, + 2000ms, + 5s, + 10s, + 20s, + 40s, + 60s, + ] + dimensions_cache_size: 100000 + aggregation_temporality: AGGREGATION_TEMPORALITY_DELTA + enable_exp_histogram: true + dimensions: + - name: service.namespace + default: default + - name: deployment.environment + default: default + # This is added to ensure the uniqueness of the timeseries + # Otherwise, identical timeseries produced by multiple replicas of + # collectors result in incorrect APM metrics + - name: signoz.collector.id + - name: service.version + - name: browser.platform + - name: browser.mobile + - name: k8s.cluster.name + - name: k8s.node.name + - name: k8s.namespace.name + - name: host.name + - name: host.type + - name: container.name +extensions: + health_check: + endpoint: 0.0.0.0:13133 + pprof: + endpoint: 0.0.0.0:1777 +exporters: + clickhousetraces: + datasource: tcp://clickhouse:9000/signoz_traces + low_cardinal_exception_grouping: ${env:LOW_CARDINAL_EXCEPTION_GROUPING} + use_new_schema: true + signozclickhousemetrics: + dsn: tcp://clickhouse:9000/signoz_metrics + clickhouselogsexporter: + dsn: tcp://clickhouse:9000/signoz_logs + timeout: 10s + use_new_schema: true + signozclickhousemeter: + dsn: tcp://clickhouse:9000/signoz_meter + timeout: 45s + sending_queue: + enabled: false + metadataexporter: + cache: + provider: in_memory + dsn: tcp://clickhouse:9000/signoz_metadata + enabled: true + timeout: 45s +service: + telemetry: + logs: + encoding: json + extensions: + - health_check + - pprof + pipelines: + traces: + receivers: [otlp] + processors: [signozspanmetrics/delta, batch] + exporters: [clickhousetraces, metadataexporter, signozmeter] + metrics: + receivers: [otlp] + processors: [batch] + exporters: [signozclickhousemetrics, metadataexporter, signozmeter] + + logs: + receivers: [otlp] + processors: [batch] + exporters: [clickhouselogsexporter, metadataexporter, signozmeter] + metrics/meter: + receivers: [signozmeter] + processors: [batch/meter] + exporters: [signozclickhousemeter] diff --git a/dev/otel-collector-opamp-config.yaml b/dev/otel-collector-opamp-config.yaml new file mode 100644 index 0000000..7267607 --- /dev/null +++ b/dev/otel-collector-opamp-config.yaml @@ -0,0 +1 @@ +server_endpoint: ws://signoz:4320/v1/opamp diff --git a/dockerfiles/main.Dockerfile b/dockerfiles/main.Dockerfile new file mode 100644 index 0000000..14843cb --- /dev/null +++ b/dockerfiles/main.Dockerfile @@ -0,0 +1,27 @@ +FROM node:25.6.1 AS production + +RUN npm i -g bun + +WORKDIR /app + +COPY package.json bun.lock turbo.json ./ + +COPY apps/main/package.json ./apps/main/package.json + +COPY packages ./packages + +RUN bun install + +COPY apps/main ./apps/main + +RUN bun install + +RUN bun run build + +COPY scripts ./scripts + +EXPOSE 3000 + +RUN chmod +x scripts/prod.start.sh + +CMD ["/bin/sh", "scripts/prod.start.sh", "apps/main"] diff --git a/dockerfiles/migrator.Dockerfile b/dockerfiles/migrator.Dockerfile new file mode 100644 index 0000000..ad9e319 --- /dev/null +++ b/dockerfiles/migrator.Dockerfile @@ -0,0 +1,17 @@ +FROM node:23.11.0 AS base + +RUN npm i -g bun + +FROM base AS primary + +WORKDIR /app + +COPY package.json bun.lock turbo.json ./ + +COPY packages/db packages/db + +RUN bun install + +COPY scripts scripts + +CMD ["/bin/sh", "/app/scripts/migrate.sh"] diff --git a/dockerfiles/processor.Dockerfile b/dockerfiles/processor.Dockerfile new file mode 100644 index 0000000..a968026 --- /dev/null +++ b/dockerfiles/processor.Dockerfile @@ -0,0 +1,26 @@ +FROM oven/bun:1.3.5-alpine + +RUN apk add --no-cache xh + +WORKDIR /app + +COPY package.json bun.lock turbo.json ./ + +COPY apps/processor/package.json ./apps/processor/package.json + +COPY packages ./packages + +RUN bun install + +COPY apps/processor ./apps/processor + +RUN bun install + +COPY scripts ./scripts + +EXPOSE 3000 +EXPOSE 9001 + +RUN chmod +x scripts/*.sh + +CMD ["/bin/sh", "scripts/prod.start.sh", "apps/processor"] diff --git a/package.json b/package.json new file mode 100644 index 0000000..ec27cab --- /dev/null +++ b/package.json @@ -0,0 +1,42 @@ +{ + "name": "@illusory/mapp", + "version": "1.0.0", + "private": true, + "engines": { + "node": ">=24" + }, + "packageManager": "bun@1.3.5", + "workspaces": [ + "apps/*", + "packages/*" + ], + "scripts": { + "build": "turbo build", + "dev": "turbo dev --concurrency=8", + "auth:schemagen": "source .env && cd packages/logic && bun run auth:schemagen", + "db:migrate": "./scripts/migrate.sh", + "prod": "turbo prod", + "test": "turbo test" + }, + "devDependencies": { + "prettier": "^3.8.1", + "prettier-plugin-sort-imports": "^1.8.11", + "prettier-plugin-svelte": "^3.5.0", + "prettier-plugin-tailwindcss": "^0.7.2", + "turbo": "^2.7.0", + "typescript": "^5.9.3" + }, + "prettier": { + "arrowParens": "always", + "singleQuote": false, + "jsxSingleQuote": false, + "semi": true, + "trailingComma": "all", + "tabWidth": 4, + "plugins": [ + "prettier-plugin-sort-imports", + "prettier-plugin-tailwindcss", + "prettier-plugin-svelte" + ] + } +} diff --git a/packages/db/drizzle.config.ts b/packages/db/drizzle.config.ts new file mode 100644 index 0000000..98dd550 --- /dev/null +++ b/packages/db/drizzle.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "drizzle-kit"; +import { settings } from "@pkg/settings"; + +export default defineConfig({ + schema: "./schema", + out: "./migrations", + dialect: "postgresql", + verbose: true, + strict: false, + dbCredentials: { + url: settings.databaseUrl, + }, +}); diff --git a/packages/db/index.ts b/packages/db/index.ts new file mode 100644 index 0000000..7bf9e59 --- /dev/null +++ b/packages/db/index.ts @@ -0,0 +1,11 @@ +import { drizzle } from "drizzle-orm/postgres-js"; +import { settings } from "@pkg/settings"; +import * as schema from "./schema"; + +const db = drizzle(settings.databaseUrl, { schema }); + +export type Database = typeof db; + +export * from "drizzle-orm"; + +export { db, schema }; diff --git a/packages/db/migrations/0000_woozy_mother_askani.sql b/packages/db/migrations/0000_woozy_mother_askani.sql new file mode 100644 index 0000000..330fc99 --- /dev/null +++ b/packages/db/migrations/0000_woozy_mother_askani.sql @@ -0,0 +1,119 @@ +CREATE TABLE IF NOT EXISTS "two_factor" ( + "id" text PRIMARY KEY NOT NULL, + "secret" text NOT NULL, + "backup_codes" json, + "user_id" text NOT NULL, + "created_at" timestamp NOT NULL, + "updated_at" timestamp NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "twofa_sessions" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "session_id" text NOT NULL, + "verification_token" text NOT NULL, + "code_used" text, + "status" varchar(16) NOT NULL, + "attempts" integer DEFAULT 0 NOT NULL, + "max_attempts" integer DEFAULT 5 NOT NULL, + "verified_at" timestamp, + "expires_at" timestamp NOT NULL, + "created_at" timestamp NOT NULL, + "ip_address" text DEFAULT '', + "user_agent" text DEFAULT '', + CONSTRAINT "twofa_sessions_verification_token_unique" UNIQUE("verification_token") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "account" ( + "id" text PRIMARY KEY NOT NULL, + "account_id" text NOT NULL, + "provider_id" text NOT NULL, + "user_id" text NOT NULL, + "access_token" text, + "refresh_token" text, + "id_token" text, + "access_token_expires_at" timestamp, + "refresh_token_expires_at" timestamp, + "scope" text, + "password" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "user" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "email" text NOT NULL, + "email_verified" boolean DEFAULT false NOT NULL, + "image" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + "username" text, + "display_username" text, + "role" text, + "banned" boolean DEFAULT false, + "ban_reason" text, + "ban_expires" timestamp, + "onboarding_done" boolean DEFAULT false, + "last2_fa_verified_at" timestamp, + "parent_id" text, + CONSTRAINT "user_email_unique" UNIQUE("email"), + CONSTRAINT "user_username_unique" UNIQUE("username") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "verification" ( + "id" text PRIMARY KEY NOT NULL, + "identifier" text NOT NULL, + "value" text NOT NULL, + "expires_at" timestamp NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "notifications" ( + "id" serial PRIMARY KEY NOT NULL, + "title" text NOT NULL, + "body" text NOT NULL, + "priority" varchar(12) DEFAULT 'normal' NOT NULL, + "type" varchar(12) NOT NULL, + "category" varchar(64), + "is_read" boolean DEFAULT false NOT NULL, + "is_archived" boolean DEFAULT false NOT NULL, + "action_url" text, + "action_type" varchar(16), + "action_data" json, + "icon" varchar(64), + "user_id" text NOT NULL, + "sent_at" timestamp NOT NULL, + "read_at" timestamp, + "expires_at" timestamp, + "created_at" timestamp NOT NULL, + "updated_at" timestamp NOT NULL +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "two_factor" ADD CONSTRAINT "two_factor_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "twofa_sessions" ADD CONSTRAINT "twofa_sessions_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "notifications" ADD CONSTRAINT "notifications_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "account_userId_idx" ON "account" USING btree ("user_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "verification_identifier_idx" ON "verification" USING btree ("identifier"); \ No newline at end of file diff --git a/packages/db/migrations/meta/0000_snapshot.json b/packages/db/migrations/meta/0000_snapshot.json new file mode 100644 index 0000000..86da0f3 --- /dev/null +++ b/packages/db/migrations/meta/0000_snapshot.json @@ -0,0 +1,655 @@ +{ + "id": "1bb75845-f9cf-41a0-96ec-10c66fdc34d6", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "two_factor_user_id_user_id_fk": { + "name": "two_factor_user_id_user_id_fk", + "tableFrom": "two_factor", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.twofa_sessions": { + "name": "twofa_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "verification_token": { + "name": "verification_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "code_used": { + "name": "code_used", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": { + "twofa_sessions_user_id_user_id_fk": { + "name": "twofa_sessions_user_id_user_id_fk", + "tableFrom": "twofa_sessions", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "twofa_sessions_verification_token_unique": { + "name": "twofa_sessions_verification_token_unique", + "nullsNotDistinct": false, + "columns": [ + "verification_token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_username": { + "name": "display_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "onboarding_done": { + "name": "onboarding_done", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "last2_fa_verified_at": { + "name": "last2_fa_verified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + }, + "user_username_unique": { + "name": "user_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notifications": { + "name": "notifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true, + "default": "'normal'" + }, + "type": { + "name": "type", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "is_read": { + "name": "is_read", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_archived": { + "name": "is_archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "action_url": { + "name": "action_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action_type": { + "name": "action_type", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "action_data": { + "name": "action_data", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "read_at": { + "name": "read_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "notifications_user_id_user_id_fk": { + "name": "notifications_user_id_user_id_fk", + "tableFrom": "notifications", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json new file mode 100644 index 0000000..ee55497 --- /dev/null +++ b/packages/db/migrations/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1769954723767, + "tag": "0000_woozy_mother_askani", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/packages/db/package.json b/packages/db/package.json new file mode 100644 index 0000000..593d43f --- /dev/null +++ b/packages/db/package.json @@ -0,0 +1,27 @@ +{ + "name": "@pkg/db", + "module": "index.ts", + "type": "module", + "scripts": { + "db:gen": "drizzle-kit generate --config=drizzle.config.ts", + "db:drop": "drizzle-kit drop --config=drizzle.config.ts", + "db:push": "drizzle-kit push --config=drizzle.config.ts", + "db:migrate": "drizzle-kit generate --config=drizzle.config.ts && drizzle-kit push --config=drizzle.config.ts", + "db:forcemigrate": "drizzle-kit generate --config=drizzle.config.ts && drizzle-kit push --config=drizzle.config.ts --force", + "dev": "drizzle-kit studio --host=0.0.0.0 --port=5420 --config=drizzle.config.ts --verbose" + }, + "dependencies": { + "@pkg/settings": "workspace:*", + "dotenv": "^16.4.7", + "drizzle-orm": "^0.36.1", + "postgres": "^3.4.8" + }, + "devDependencies": { + "@types/bun": "latest", + "@types/pg": "^8.11.10", + "drizzle-kit": "^0.28.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } +} diff --git a/packages/db/schema/auth.schema.ts b/packages/db/schema/auth.schema.ts new file mode 100644 index 0000000..0001481 --- /dev/null +++ b/packages/db/schema/auth.schema.ts @@ -0,0 +1,52 @@ +import { + integer, + json, + pgTable, + text, + timestamp, + varchar, +} from "drizzle-orm/pg-core"; +import { user } from "./better.auth.schema"; +import { relations } from "drizzle-orm"; + +export const twoFactor = pgTable("two_factor", { + id: text("id").primaryKey(), + secret: text("secret").notNull(), + backupCodes: json("backup_codes").$type(), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + createdAt: timestamp("created_at").notNull(), + updatedAt: timestamp("updated_at").notNull(), +}); + +export const twofaSessions = pgTable("twofa_sessions", { + id: text("id").primaryKey(), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + sessionId: text("session_id").notNull(), // Better Auth session ID + + // Verification Tracking + verificationToken: text("verification_token").notNull().unique(), // Unique nonce for this attempt + codeUsed: text("code_used"), // The TOTP code submitted (prevent replay) + status: varchar("status", { length: 16 }).notNull(), // "pending" | "verified" | "failed" | "expired" + + attempts: integer("attempts").default(0).notNull(), + maxAttempts: integer("max_attempts").default(5).notNull(), + + verifiedAt: timestamp("verified_at"), + expiresAt: timestamp("expires_at").notNull(), + createdAt: timestamp("created_at").notNull(), + + // Security Audit + ipAddress: text("ip_address").default(""), + userAgent: text("user_agent").default(""), +}); + +export const twofaSessionsRelations = relations(twofaSessions, ({ one }) => ({ + userAccount: one(user, { + fields: [twofaSessions.userId], + references: [user.id], + }), +})); diff --git a/packages/db/schema/better.auth.schema.ts b/packages/db/schema/better.auth.schema.ts new file mode 100644 index 0000000..d162dc1 --- /dev/null +++ b/packages/db/schema/better.auth.schema.ts @@ -0,0 +1,75 @@ +import { boolean, index, pgTable, text, timestamp } from "drizzle-orm/pg-core"; +import { relations } from "drizzle-orm"; + +export const user = pgTable("user", { + id: text("id").primaryKey(), + name: text("name").notNull(), + email: text("email").notNull().unique(), + emailVerified: boolean("email_verified").default(false).notNull(), + image: text("image"), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at") + .defaultNow() + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + username: text("username").unique(), + displayUsername: text("display_username"), + role: text("role"), + banned: boolean("banned").default(false), + banReason: text("ban_reason"), + banExpires: timestamp("ban_expires"), + onboardingDone: boolean("onboarding_done").default(false), + last2FAVerifiedAt: timestamp("last2_fa_verified_at"), + parentId: text("parent_id"), +}); + +export const account = pgTable( + "account", + { + id: text("id").primaryKey(), + accountId: text("account_id").notNull(), + providerId: text("provider_id").notNull(), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + accessToken: text("access_token"), + refreshToken: text("refresh_token"), + idToken: text("id_token"), + accessTokenExpiresAt: timestamp("access_token_expires_at"), + refreshTokenExpiresAt: timestamp("refresh_token_expires_at"), + scope: text("scope"), + password: text("password"), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at") + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + }, + (table) => [index("account_userId_idx").on(table.userId)], +); + +export const verification = pgTable( + "verification", + { + id: text("id").primaryKey(), + identifier: text("identifier").notNull(), + value: text("value").notNull(), + expiresAt: timestamp("expires_at").notNull(), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at") + .defaultNow() + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + }, + (table) => [index("verification_identifier_idx").on(table.identifier)], +); + +export const userRelations = relations(user, ({ many }) => ({ + accounts: many(account), +})); + +export const accountRelations = relations(account, ({ one }) => ({ + user: one(user, { + fields: [account.userId], + references: [user.id], + }), +})); diff --git a/packages/db/schema/general.schema.ts b/packages/db/schema/general.schema.ts new file mode 100644 index 0000000..b8cf6ab --- /dev/null +++ b/packages/db/schema/general.schema.ts @@ -0,0 +1,49 @@ +import { + boolean, + json, + pgTable, + serial, + text, + timestamp, + varchar, +} from "drizzle-orm/pg-core"; +import { user } from "./better.auth.schema"; +import { relations } from "drizzle-orm"; + +export const notifications = pgTable("notifications", { + id: serial("id").primaryKey(), + + title: text("title").notNull(), + body: text("body").notNull(), + priority: varchar("priority", { length: 12 }).default("normal").notNull(), // "low", "normal", "high", "urgent" + + type: varchar("type", { length: 12 }).notNull(), + category: varchar("category", { length: 64 }), + + isRead: boolean("is_read").default(false).notNull(), + isArchived: boolean("is_archived").default(false).notNull(), + + actionUrl: text("action_url"), // URL to navigate to when clicked + actionType: varchar("action_type", { length: 16 }), // Type of action ("link", "function", etc.) + actionData: json("action_data"), // Any additional data for the action + + icon: varchar("icon", { length: 64 }), // Optional icon identifier + + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + + // Lifecycle management + sentAt: timestamp("sent_at").notNull(), + readAt: timestamp("read_at"), + expiresAt: timestamp("expires_at"), + createdAt: timestamp("created_at").notNull(), + updatedAt: timestamp("updated_at").notNull(), +}); + +export const notificationsRelations = relations(notifications, ({ one }) => ({ + userAccount: one(user, { + fields: [notifications.userId], + references: [user.id], + }), +})); diff --git a/packages/db/schema/index.ts b/packages/db/schema/index.ts new file mode 100644 index 0000000..2171de4 --- /dev/null +++ b/packages/db/schema/index.ts @@ -0,0 +1,3 @@ +export * from "./auth.schema"; +export * from "./better.auth.schema"; +export * from "./general.schema"; diff --git a/packages/logger/client.ts b/packages/logger/client.ts new file mode 100644 index 0000000..b246456 --- /dev/null +++ b/packages/logger/client.ts @@ -0,0 +1,222 @@ +type LogLevel = "error" | "warn" | "info" | "http" | "debug"; + +export interface LogEntry { + level: LogLevel; + timestamp: string; + message: any; + metadata?: any; +} + +interface Error { + code: string; + message: string; + description?: string; + detail?: string; + error?: any; + actionable?: boolean; +} + +class BrowserLogger { + private queue: LogEntry[] = []; + private timer: ReturnType | null = null; + private readonly BATCH_INTERVAL = 5000; // 5 seconds + private readonly BATCH_SIZE_LIMIT = 50; + private readonly isDev: boolean; + + constructor(isDev: boolean) { + this.isDev = isDev; + if (!this.isDev) { + this.startBatchTimer(); + this.setupBeforeUnloadHandler(); + } + } + + private startBatchTimer() { + this.timer = setInterval(() => this.flush(), this.BATCH_INTERVAL); + } + + private setupBeforeUnloadHandler() { + // Flush logs before page unload to avoid losing them + if (typeof window !== "undefined") { + window.addEventListener("beforeunload", () => { + this.flushSync(); + }); + } + } + + private async flush() { + if (this.queue.length === 0) return; + + const batch = [...this.queue]; + this.queue = []; + + try { + // Forward batch to Hono route + await fetch("/api/logs", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ logs: batch }), + }); + } catch (err) { + console.error("Axiom batch upload failed", err); + // Re-add failed logs back to queue (up to a limit to avoid memory issues) + if (this.queue.length < this.BATCH_SIZE_LIMIT * 2) { + this.queue.push(...batch); + } + } + } + + private flushSync() { + // Synchronous flush for beforeunload using sendBeacon + if (this.queue.length === 0) return; + + const batch = [...this.queue]; + this.queue = []; + + try { + const blob = new Blob([JSON.stringify({ logs: batch })], { + type: "application/json", + }); + navigator.sendBeacon("/api/logs", blob); + } catch (err) { + console.error("Failed to send logs via sendBeacon", err); + } + } + + private serializeError(error: unknown): any { + if (error instanceof Error) { + return { + name: error.name, + message: error.message, + stack: error.stack, + ...(error.cause && { cause: this.serializeError(error.cause) }), + }; + } + return error; + } + + private createLogEntry( + level: LogLevel, + message: any, + metadata?: any, + ): LogEntry { + // Handle Error serialization for message + const cleanMessage = + message instanceof Error ? this.serializeError(message) : message; + + // Handle Error serialization for metadata + const cleanMetadata = + metadata instanceof Error + ? this.serializeError(metadata) + : metadata; + + return { + level, + timestamp: new Date().toISOString(), + message: cleanMessage, + metadata: cleanMetadata || {}, + }; + } + + private async sendLog(entry: LogEntry) { + // Always log errors to console, even in production (for user debugging) + // In dev, log everything to console + const shouldConsoleLog = this.isDev || entry.level === "error"; + + if (shouldConsoleLog) { + const consoleMethod = + entry.level === "http" ? "debug" : entry.level; + console[consoleMethod]( + `[client-${entry.level}] ${entry.timestamp}:`, + entry.message, + entry.metadata && Object.keys(entry.metadata).length > 0 + ? entry.metadata + : "", + ); + } + + // In production, add to queue for batching + if (!this.isDev) { + this.queue.push(entry); + + // Safety flush if queue gets too large + if (this.queue.length >= this.BATCH_SIZE_LIMIT) { + await this.flush(); + } + } + } + + error(message: any, metadata?: any) { + this.sendLog(this.createLogEntry("error", message, metadata)); + } + + warn(message: any, metadata?: any) { + this.sendLog(this.createLogEntry("warn", message, metadata)); + } + + info(message: any, metadata?: any) { + this.sendLog(this.createLogEntry("info", message, metadata)); + } + + http(message: any, metadata?: any) { + this.sendLog(this.createLogEntry("http", message, metadata)); + } + + debug(message: any, metadata?: any) { + this.sendLog(this.createLogEntry("debug", message, metadata)); + } + + // Manual flush method for advanced use cases + async forceFlush() { + await this.flush(); + } + + // Cleanup method + destroy() { + if (this.timer) { + clearInterval(this.timer); + this.timer = null; + } + this.flushSync(); + } +} + +/** + * Factory function to create a BrowserLogger instance + * + * @param isDev - Whether the app is running in development mode + * @returns A new BrowserLogger instance + * + * @example + * // SvelteKit + * import { dev } from '$app/environment'; + * const logger = getLoggerInstance(dev); + * + * @example + * // Next.js + * const logger = getLoggerInstance(process.env.NODE_ENV === 'development'); + * + * @example + * // Vite + * const logger = getLoggerInstance(import.meta.env.DEV); + */ +function getLoggerInstance(isDev: boolean): BrowserLogger { + return new BrowserLogger(isDev); +} + +function getError(logger: BrowserLogger, payload: Error, error?: any) { + logger.error(payload); + if (error) { + logger.error(error); + } + return { + code: payload.code, + message: payload.message, + description: payload.description, + detail: payload.detail, + error: error, + actionable: payload.actionable, + } as Error; +} + +export { getError, getLoggerInstance }; diff --git a/packages/logger/index.ts b/packages/logger/index.ts new file mode 100644 index 0000000..6a07c24 --- /dev/null +++ b/packages/logger/index.ts @@ -0,0 +1,143 @@ +import DailyRotateFile from "winston-daily-rotate-file"; +import { settings } from "@pkg/settings"; +import type { Err } from "@pkg/result"; +import winston from "winston"; +import util from "util"; +import path from "path"; + +process.on("warning", (warning) => { + const msg = String(warning?.message || ""); + const name = String((warning as any)?.name || ""); + + // Ignore the noisy timer warning from Node/kafkajs interplay + if ( + name === "TimeoutNegativeWarning" || + msg.includes("TimeoutNegativeWarning") || + msg.includes("Timeout duration was set to 1") + ) { + return; + } + + // Keep other warnings visible + console.warn(warning); +}); + +const levels = { + error: 0, + warn: 1, + info: 2, + http: 3, + debug: 4, +}; + +const colors = { + error: "red", + warn: "yellow", + info: "green", + http: "magenta", + debug: "white", +}; + +const level = () => { + const envLevel = process.env.LOG_LEVEL?.toLowerCase(); + if (envLevel && envLevel in levels) { + return envLevel; + } + return settings.isDevelopment ? "debug" : "warn"; +}; + +// Console format with colors +const consoleFormat = winston.format.combine( + winston.format.errors({ stack: true }), + winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss:ms" }), + winston.format.colorize({ all: true }), + winston.format.printf((info) => { + const { level, message, timestamp, ...extra } = info; + + let formattedMessage = ""; + if (message instanceof Error) { + formattedMessage = message.stack || message.message; + } else if (typeof message === "object") { + formattedMessage = util.inspect(message, { + depth: null, + colors: true, + }); + } else { + formattedMessage = message as any as string; + } + + // Handle extra fields (if any) + const formattedExtra = + Object.keys(extra).length > 0 + ? `\n${util.inspect(extra, { depth: null, colors: true })}` + : ""; + + return `[${level}] ${timestamp}: ${formattedMessage}${formattedExtra}`; + }), +); + +// JSON format for file logging +const fileFormat = winston.format.combine( + winston.format.errors({ stack: true }), + winston.format.timestamp(), + winston.format.json(), +); + +// File transport with daily rotation +const fileTransport = new DailyRotateFile({ + filename: path.join("logs", "app-%DATE%.log"), + datePattern: "YYYY-MM-DD", + maxSize: "20m", + maxFiles: "14d", + format: fileFormat, +}); + +// Error file transport with daily rotation +const errorFileTransport = new DailyRotateFile({ + filename: path.join("logs", "error-%DATE%.log"), + datePattern: "YYYY-MM-DD", + maxSize: "20m", + maxFiles: "14d", + level: "error", + format: fileFormat, +}); + +const transports: winston.transport[] = [ + new winston.transports.Console({ format: consoleFormat }), + fileTransport, + errorFileTransport, +]; + +winston.addColors(colors); + +const logger = winston.createLogger({ + level: level(), + levels, + transports, + format: fileFormat, + exceptionHandlers: [ + new winston.transports.Console({ format: consoleFormat }), + fileTransport, + ], + rejectionHandlers: [ + new winston.transports.Console({ format: consoleFormat }), + fileTransport, + ], +}); + +const stream = { write: (message: string) => logger.http(message.trim()) }; + +function getError(payload: Err, error?: any) { + logger.error(JSON.stringify({ payload, error }, null, 2)); + console.error(error); + return { + code: payload.code, + message: payload.message, + description: payload.description, + detail: payload.detail, + error: error instanceof Error ? error.message : error, + actionable: payload.actionable, + } as Err; +} + +export { getError, logger, stream }; diff --git a/packages/logger/package.json b/packages/logger/package.json new file mode 100644 index 0000000..d9bdcbe --- /dev/null +++ b/packages/logger/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pkg/logger", + "module": "index.ts", + "type": "module", + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "@axiomhq/winston": "^1.3.1", + "@pkg/result": "workspace:*", + "@pkg/settings": "workspace:*", + "winston": "^3.17.0", + "winston-daily-rotate-file": "^5.0.0" + } +} diff --git a/packages/logger/tsconfig.json b/packages/logger/tsconfig.json new file mode 100644 index 0000000..0f8d8dd --- /dev/null +++ b/packages/logger/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + }, +} diff --git a/packages/logic/core/array.utils.ts b/packages/logic/core/array.utils.ts new file mode 100644 index 0000000..237c4a5 --- /dev/null +++ b/packages/logic/core/array.utils.ts @@ -0,0 +1,7 @@ +export function chunk(arr: T[], size: number): T[][] { + const result = []; + for (let i = 0; i < arr.length; i += size) { + result.push(arr.slice(i, i + size)); + } + return result; +} diff --git a/packages/logic/core/data/countries.ts b/packages/logic/core/data/countries.ts new file mode 100644 index 0000000..be0a9e0 --- /dev/null +++ b/packages/logic/core/data/countries.ts @@ -0,0 +1,264 @@ +export const COUNTRIES = [ + { id: "1", name: "Afghanistan", code: "AF" }, + { id: "2", name: "Albania", code: "AL" }, + { id: "3", name: "Algeria", code: "DZ" }, + { id: "4", name: "American Samoa", code: "AS" }, + { id: "5", name: "Andorra", code: "AD" }, + { id: "6", name: "Angola", code: "AO" }, + { id: "7", name: "Anguilla", code: "AI" }, + { id: "8", name: "Antarctica", code: "AQ" }, + { id: "9", name: "Antigua and Barbuda", code: "AG" }, + { id: "10", name: "Argentina", code: "AR" }, + { id: "11", name: "Armenia", code: "AM" }, + { id: "12", name: "Aruba", code: "AW" }, + { id: "13", name: "Australia", code: "AU" }, + { id: "14", name: "Austria", code: "AT" }, + { id: "15", name: "Azerbaijan", code: "AZ" }, + { id: "16", name: "Bahamas", code: "BS" }, + { id: "17", name: "Bahrain", code: "BH" }, + { id: "18", name: "Bangladesh", code: "BD" }, + { id: "19", name: "Barbados", code: "BB" }, + { id: "20", name: "Belarus", code: "BY" }, + { id: "21", name: "Belgium", code: "BE" }, + { id: "22", name: "Belize", code: "BZ" }, + { id: "23", name: "Benin", code: "BJ" }, + { id: "24", name: "Bermuda", code: "BM" }, + { id: "25", name: "Bhutan", code: "BT" }, + { id: "26", name: "Bolivia", code: "BO" }, + { id: "27", name: "Bosnia and Herzegovina", code: "BA" }, + { id: "28", name: "Botswana", code: "BW" }, + { id: "29", name: "Bouvet Island", code: "BV" }, + { id: "30", name: "Brazil", code: "BR" }, + { id: "31", name: "British Indian Ocean Territory", code: "IO" }, + { id: "32", name: "British Virgin Islands", code: "VG" }, + { id: "33", name: "Brunei", code: "BN" }, + { id: "34", name: "Bulgaria", code: "BG" }, + { id: "35", name: "Burkina Faso", code: "BF" }, + { id: "36", name: "Burundi", code: "BI" }, + { id: "37", name: "Cambodia", code: "KH" }, + { id: "38", name: "Cameroon", code: "CM" }, + { id: "39", name: "Canada", code: "CA" }, + { id: "40", name: "Cape Verde", code: "CV" }, + { id: "41", name: "Caribbean Netherlands", code: "BQ" }, + { id: "42", name: "Cayman Islands", code: "KY" }, + { id: "43", name: "Central African Republic", code: "CF" }, + { id: "44", name: "Chad", code: "TD" }, + { id: "45", name: "Chile", code: "CL" }, + { id: "46", name: "China", code: "CN" }, + { id: "47", name: "Christmas Island", code: "CX" }, + { id: "48", name: "Cocos (Keeling) Islands", code: "CC" }, + { id: "49", name: "Colombia", code: "CO" }, + { id: "50", name: "Comoros", code: "KM" }, + { id: "51", name: "Cook Islands", code: "CK" }, + { id: "52", name: "Costa Rica", code: "CR" }, + { id: "53", name: "Croatia", code: "HR" }, + { id: "54", name: "Cuba", code: "CU" }, + { id: "55", name: "Curaçao", code: "CW" }, + { id: "56", name: "Cyprus", code: "CY" }, + { id: "57", name: "Czechia", code: "CZ" }, + { id: "58", name: "DR Congo", code: "CD" }, + { id: "59", name: "Denmark", code: "DK" }, + { id: "60", name: "Djibouti", code: "DJ" }, + { id: "61", name: "Dominica", code: "DM" }, + { id: "62", name: "Dominican Republic", code: "DO" }, + { id: "63", name: "Ecuador", code: "EC" }, + { id: "64", name: "Egypt", code: "EG" }, + { id: "65", name: "El Salvador", code: "SV" }, + { id: "66", name: "Equatorial Guinea", code: "GQ" }, + { id: "67", name: "Eritrea", code: "ER" }, + { id: "68", name: "Estonia", code: "EE" }, + { id: "69", name: "Eswatini", code: "SZ" }, + { id: "70", name: "Ethiopia", code: "ET" }, + { id: "71", name: "Falkland Islands", code: "FK" }, + { id: "72", name: "Faroe Islands", code: "FO" }, + { id: "73", name: "Fiji", code: "FJ" }, + { id: "74", name: "Finland", code: "FI" }, + { id: "75", name: "France", code: "FR" }, + { id: "76", name: "French Guiana", code: "GF" }, + { id: "77", name: "French Polynesia", code: "PF" }, + { id: "78", name: "French Southern and Antarctic Lands", code: "TF" }, + { id: "79", name: "Gabon", code: "GA" }, + { id: "80", name: "Gambia", code: "GM" }, + { id: "81", name: "Georgia", code: "GE" }, + { id: "82", name: "Germany", code: "DE" }, + { id: "83", name: "Ghana", code: "GH" }, + { id: "84", name: "Gibraltar", code: "GI" }, + { id: "85", name: "Greece", code: "GR" }, + { id: "86", name: "Greenland", code: "GL" }, + { id: "87", name: "Grenada", code: "GD" }, + { id: "88", name: "Guadeloupe", code: "GP" }, + { id: "89", name: "Guam", code: "GU" }, + { id: "90", name: "Guatemala", code: "GT" }, + { id: "91", name: "Guernsey", code: "GG" }, + { id: "92", name: "Guinea", code: "GN" }, + { id: "93", name: "Guinea-Bissau", code: "GW" }, + { id: "94", name: "Guyana", code: "GY" }, + { id: "95", name: "Haiti", code: "HT" }, + { id: "96", name: "Heard Island and McDonald Islands", code: "HM" }, + { id: "97", name: "Honduras", code: "HN" }, + { id: "98", name: "Hong Kong", code: "HK" }, + { id: "99", name: "Hungary", code: "HU" }, + { id: "100", name: "Iceland", code: "IS" }, + { id: "101", name: "India", code: "IN" }, + { id: "102", name: "Indonesia", code: "ID" }, + { id: "103", name: "Iran", code: "IR" }, + { id: "104", name: "Iraq", code: "IQ" }, + { id: "105", name: "Ireland", code: "IE" }, + { id: "106", name: "Isle of Man", code: "IM" }, + { id: "107", name: "Israel", code: "IL" }, + { id: "108", name: "Italy", code: "IT" }, + { id: "109", name: "Ivory Coast", code: "CI" }, + { id: "110", name: "Jamaica", code: "JM" }, + { id: "111", name: "Japan", code: "JP" }, + { id: "112", name: "Jersey", code: "JE" }, + { id: "113", name: "Jordan", code: "JO" }, + { id: "114", name: "Kazakhstan", code: "KZ" }, + { id: "115", name: "Kenya", code: "KE" }, + { id: "116", name: "Kiribati", code: "KI" }, + { id: "117", name: "Kosovo", code: "XK" }, + { id: "118", name: "Kuwait", code: "KW" }, + { id: "119", name: "Kyrgyzstan", code: "KG" }, + { id: "120", name: "Laos", code: "LA" }, + { id: "121", name: "Latvia", code: "LV" }, + { id: "122", name: "Lebanon", code: "LB" }, + { id: "123", name: "Lesotho", code: "LS" }, + { id: "124", name: "Liberia", code: "LR" }, + { id: "125", name: "Libya", code: "LY" }, + { id: "126", name: "Liechtenstein", code: "LI" }, + { id: "127", name: "Lithuania", code: "LT" }, + { id: "128", name: "Luxembourg", code: "LU" }, + { id: "129", name: "Macau", code: "MO" }, + { id: "130", name: "Madagascar", code: "MG" }, + { id: "131", name: "Malawi", code: "MW" }, + { id: "132", name: "Malaysia", code: "MY" }, + { id: "133", name: "Maldives", code: "MV" }, + { id: "134", name: "Mali", code: "ML" }, + { id: "135", name: "Malta", code: "MT" }, + { id: "136", name: "Marshall Islands", code: "MH" }, + { id: "137", name: "Martinique", code: "MQ" }, + { id: "138", name: "Mauritania", code: "MR" }, + { id: "139", name: "Mauritius", code: "MU" }, + { id: "140", name: "Mayotte", code: "YT" }, + { id: "141", name: "Mexico", code: "MX" }, + { id: "142", name: "Micronesia", code: "FM" }, + { id: "143", name: "Moldova", code: "MD" }, + { id: "144", name: "Monaco", code: "MC" }, + { id: "145", name: "Mongolia", code: "MN" }, + { id: "146", name: "Montenegro", code: "ME" }, + { id: "147", name: "Montserrat", code: "MS" }, + { id: "148", name: "Morocco", code: "MA" }, + { id: "149", name: "Mozambique", code: "MZ" }, + { id: "150", name: "Myanmar", code: "MM" }, + { id: "151", name: "Namibia", code: "NA" }, + { id: "152", name: "Nauru", code: "NR" }, + { id: "153", name: "Nepal", code: "NP" }, + { id: "154", name: "Netherlands", code: "NL" }, + { id: "155", name: "New Caledonia", code: "NC" }, + { id: "156", name: "New Zealand", code: "NZ" }, + { id: "157", name: "Nicaragua", code: "NI" }, + { id: "158", name: "Niger", code: "NE" }, + { id: "159", name: "Nigeria", code: "NG" }, + { id: "160", name: "Niue", code: "NU" }, + { id: "161", name: "Norfolk Island", code: "NF" }, + { id: "162", name: "North Korea", code: "KP" }, + { id: "163", name: "North Macedonia", code: "MK" }, + { id: "164", name: "Northern Mariana Islands", code: "MP" }, + { id: "165", name: "Norway", code: "NO" }, + { id: "166", name: "Oman", code: "OM" }, + { id: "167", name: "Pakistan", code: "PK" }, + { id: "168", name: "Palau", code: "PW" }, + { id: "169", name: "Palestine", code: "PS" }, + { id: "170", name: "Panama", code: "PA" }, + { id: "171", name: "Papua New Guinea", code: "PG" }, + { id: "172", name: "Paraguay", code: "PY" }, + { id: "173", name: "Peru", code: "PE" }, + { id: "174", name: "Philippines", code: "PH" }, + { id: "175", name: "Pitcairn Islands", code: "PN" }, + { id: "176", name: "Poland", code: "PL" }, + { id: "177", name: "Portugal", code: "PT" }, + { id: "178", name: "Puerto Rico", code: "PR" }, + { id: "179", name: "Qatar", code: "QA" }, + { id: "180", name: "Republic of the Congo", code: "CG" }, + { id: "181", name: "Romania", code: "RO" }, + { id: "182", name: "Russia", code: "RU" }, + { id: "183", name: "Rwanda", code: "RW" }, + { id: "184", name: "Réunion", code: "RE" }, + { id: "185", name: "Saint Barthélemy", code: "BL" }, + { + id: "186", + name: "Saint Helena, Ascension and Tristan da Cunha", + code: "SH", + }, + { id: "187", name: "Saint Kitts and Nevis", code: "KN" }, + { id: "188", name: "Saint Lucia", code: "LC" }, + { id: "189", name: "Saint Martin", code: "MF" }, + { id: "190", name: "Saint Pierre and Miquelon", code: "PM" }, + { id: "191", name: "Saint Vincent and the Grenadines", code: "VC" }, + { id: "192", name: "Samoa", code: "WS" }, + { id: "193", name: "San Marino", code: "SM" }, + { id: "194", name: "Saudi Arabia", code: "SA" }, + { id: "195", name: "Senegal", code: "SN" }, + { id: "196", name: "Serbia", code: "RS" }, + { id: "197", name: "Seychelles", code: "SC" }, + { id: "198", name: "Sierra Leone", code: "SL" }, + { id: "199", name: "Singapore", code: "SG" }, + { id: "200", name: "Sint Maarten", code: "SX" }, + { id: "201", name: "Slovakia", code: "SK" }, + { id: "202", name: "Slovenia", code: "SI" }, + { id: "203", name: "Solomon Islands", code: "SB" }, + { id: "204", name: "Somalia", code: "SO" }, + { id: "205", name: "South Africa", code: "ZA" }, + { id: "206", name: "South Georgia", code: "GS" }, + { id: "207", name: "South Korea", code: "KR" }, + { id: "208", name: "South Sudan", code: "SS" }, + { id: "209", name: "Spain", code: "ES" }, + { id: "210", name: "Sri Lanka", code: "LK" }, + { id: "211", name: "Sudan", code: "SD" }, + { id: "212", name: "Suriname", code: "SR" }, + { id: "213", name: "Svalbard and Jan Mayen", code: "SJ" }, + { id: "214", name: "Sweden", code: "SE" }, + { id: "215", name: "Switzerland", code: "CH" }, + { id: "216", name: "Syria", code: "SY" }, + { id: "217", name: "São Tomé and Príncipe", code: "ST" }, + { id: "218", name: "Taiwan", code: "TW" }, + { id: "219", name: "Tajikistan", code: "TJ" }, + { id: "220", name: "Tanzania", code: "TZ" }, + { id: "221", name: "Thailand", code: "TH" }, + { id: "222", name: "Timor-Leste", code: "TL" }, + { id: "223", name: "Togo", code: "TG" }, + { id: "224", name: "Tokelau", code: "TK" }, + { id: "225", name: "Tonga", code: "TO" }, + { id: "226", name: "Trinidad and Tobago", code: "TT" }, + { id: "227", name: "Tunisia", code: "TN" }, + { id: "228", name: "Turkey", code: "TR" }, + { id: "229", name: "Turkmenistan", code: "TM" }, + { id: "230", name: "Turks and Caicos Islands", code: "TC" }, + { id: "231", name: "Tuvalu", code: "TV" }, + { id: "232", name: "Uganda", code: "UG" }, + { id: "233", name: "Ukraine", code: "UA" }, + { id: "234", name: "United Arab Emirates", code: "AE" }, + { id: "235", name: "United Kingdom", code: "GB" }, + { id: "236", name: "United States", code: "US" }, + { id: "237", name: "United States Minor Outlying Islands", code: "UM" }, + { id: "238", name: "United States Virgin Islands", code: "VI" }, + { id: "239", name: "Uruguay", code: "UY" }, + { id: "240", name: "Uzbekistan", code: "UZ" }, + { id: "241", name: "Vanuatu", code: "VU" }, + { id: "242", name: "Vatican City", code: "VA" }, + { id: "243", name: "Venezuela", code: "VE" }, + { id: "244", name: "Vietnam", code: "VN" }, + { id: "245", name: "Wallis and Futuna", code: "WF" }, + { id: "246", name: "Western Sahara", code: "EH" }, + { id: "247", name: "Yemen", code: "YE" }, + { id: "248", name: "Zambia", code: "ZM" }, + { id: "249", name: "Zimbabwe", code: "ZW" }, + { id: "250", name: "Åland Islands", code: "AX" }, +]; + +export const COUNTRIES_SELECT = COUNTRIES.map((c) => { + return { + id: c.id, + label: `${c.code} (${c.name})`, + value: c.name.toLowerCase(), + }; +}); diff --git a/packages/logic/core/data/phonecc.ts b/packages/logic/core/data/phonecc.ts new file mode 100644 index 0000000..87d4c86 --- /dev/null +++ b/packages/logic/core/data/phonecc.ts @@ -0,0 +1,1227 @@ +export const PHONE_COUNTRY_CODES = [ + { + countryCode: "af", + country: "Afghanistan", + phoneCode: "+93", + }, + { + countryCode: "ax", + country: "Åland Islands", + phoneCode: "+358", + }, + { + countryCode: "al", + country: "Albania", + phoneCode: "+355", + }, + { + countryCode: "dz", + country: "Algeria", + phoneCode: "+213", + }, + { + countryCode: "as", + country: "American Samoa", + phoneCode: "+1", + }, + { + countryCode: "ad", + country: "Andorra", + phoneCode: "+376", + }, + { + countryCode: "ao", + country: "Angola", + phoneCode: "+244", + }, + { + countryCode: "ai", + country: "Anguilla", + phoneCode: "+1", + }, + { + countryCode: "aq", + country: "Antarctica", + phoneCode: "+672", + }, + { + countryCode: "ag", + country: "Antigua and Barbuda", + phoneCode: "+1", + }, + { + countryCode: "ar", + country: "Argentina", + phoneCode: "+54", + }, + { + countryCode: "am", + country: "Armenia", + phoneCode: "+374", + }, + { + countryCode: "aw", + country: "Aruba", + phoneCode: "+297", + }, + { + countryCode: "au", + country: "Australia", + phoneCode: "+61", + }, + { + countryCode: "at", + country: "Austria", + phoneCode: "+43", + }, + { + countryCode: "az", + country: "Azerbaijan", + phoneCode: "+994", + }, + { + countryCode: "bs", + country: "Bahamas", + phoneCode: "+1", + }, + { + countryCode: "bh", + country: "Bahrain", + phoneCode: "+973", + }, + { + countryCode: "bd", + country: "Bangladesh", + phoneCode: "+880", + }, + { + countryCode: "bb", + country: "Barbados", + phoneCode: "+1", + }, + { + countryCode: "by", + country: "Belarus", + phoneCode: "+375", + }, + { + countryCode: "be", + country: "Belgium", + phoneCode: "+32", + }, + { + countryCode: "bz", + country: "Belize", + phoneCode: "+501", + }, + { + countryCode: "bj", + country: "Benin", + phoneCode: "+229", + }, + { + countryCode: "bm", + country: "Bermuda", + phoneCode: "+1", + }, + { + countryCode: "bt", + country: "Bhutan", + phoneCode: "+975", + }, + { + countryCode: "bo", + country: "Bolivia", + phoneCode: "+591", + }, + { + countryCode: "ba", + country: "Bosnia and Herzegovina", + phoneCode: "+387", + }, + { + countryCode: "bw", + country: "Botswana", + phoneCode: "+267", + }, + { + countryCode: "br", + country: "Brazil", + phoneCode: "+55", + }, + { + countryCode: "io", + country: "British Indian Ocean Territory", + phoneCode: "+246", + }, + { + countryCode: "vg", + country: "British Virgin Islands", + phoneCode: "+1", + }, + { + countryCode: "bn", + country: "Brunei", + phoneCode: "+673", + }, + { + countryCode: "bg", + country: "Bulgaria", + phoneCode: "+359", + }, + { + countryCode: "bf", + country: "Burkina Faso", + phoneCode: "+226", + }, + { + countryCode: "bi", + country: "Burundi", + phoneCode: "+257", + }, + { + countryCode: "kh", + country: "Cambodia", + phoneCode: "+855", + }, + { + countryCode: "cm", + country: "Cameroon", + phoneCode: "+237", + }, + { + countryCode: "ca", + country: "Canada", + phoneCode: "+1", + }, + { + countryCode: "cv", + country: "Cape Verde", + phoneCode: "+238", + }, + { + countryCode: "ky", + country: "Cayman Islands", + phoneCode: "+1", + }, + { + countryCode: "cf", + country: "Central African Republic", + phoneCode: "+236", + }, + { + countryCode: "td", + country: "Chad", + phoneCode: "+235", + }, + { + countryCode: "cl", + country: "Chile", + phoneCode: "+56", + }, + { + countryCode: "cn", + country: "China", + phoneCode: "+86", + }, + { + countryCode: "cx", + country: "Christmas Island", + phoneCode: "+61", + }, + { + countryCode: "cc", + country: "Cocos [Keeling] Islands", + phoneCode: "+61", + }, + { + countryCode: "co", + country: "Colombia", + phoneCode: "+57", + }, + { + countryCode: "km", + country: "Comoros", + phoneCode: "+269", + }, + { + countryCode: "cg", + country: "Congo - Brazzaville", + phoneCode: "+242", + }, + { + countryCode: "cd", + country: "Congo - Kinshasa", + phoneCode: "+243", + }, + { + countryCode: "ck", + country: "Cook Islands", + phoneCode: "+682", + }, + { + countryCode: "cr", + country: "Costa Rica", + phoneCode: "+506", + }, + { + countryCode: "ci", + country: "Côte d’Ivoire", + phoneCode: "+225", + }, + { + countryCode: "hr", + country: "Croatia", + phoneCode: "+385", + }, + { + countryCode: "cy", + country: "Cyprus", + phoneCode: "+357", + }, + { + countryCode: "cz", + country: "Czech Republic", + phoneCode: "+420", + }, + { + countryCode: "dk", + country: "Denmark", + phoneCode: "+45", + }, + { + countryCode: "dj", + country: "Djibouti", + phoneCode: "+253", + }, + { + countryCode: "dm", + country: "Dominica", + phoneCode: "+1", + }, + { + countryCode: "do", + country: "Dominican Republic", + phoneCode: "+1", + }, + { + countryCode: "ec", + country: "Ecuador", + phoneCode: "+593", + }, + { + countryCode: "eg", + country: "Egypt", + phoneCode: "+20", + }, + { + countryCode: "sv", + country: "El Salvador", + phoneCode: "+503", + }, + { + countryCode: "gq", + country: "Equatorial Guinea", + phoneCode: "+240", + }, + { + countryCode: "er", + country: "Eritrea", + phoneCode: "+291", + }, + { + countryCode: "ee", + country: "Estonia", + phoneCode: "+372", + }, + { + countryCode: "et", + country: "Ethiopia", + phoneCode: "+251", + }, + { + countryCode: "fk", + country: "Falkland Islands", + phoneCode: "+500", + }, + { + countryCode: "fo", + country: "Faroe Islands", + phoneCode: "+298", + }, + { + countryCode: "fj", + country: "Fiji", + phoneCode: "+679", + }, + { + countryCode: "fi", + country: "Finland", + phoneCode: "+358", + }, + { + countryCode: "fr", + country: "France", + phoneCode: "+33", + }, + { + countryCode: "gf", + country: "French Guiana", + phoneCode: "+594", + }, + { + countryCode: "pf", + country: "French Polynesia", + phoneCode: "+689", + }, + { + countryCode: "tf", + country: "French Southern Territories", + phoneCode: "+262", + }, + { + countryCode: "ga", + country: "Gabon", + phoneCode: "+241", + }, + { + countryCode: "gm", + country: "Gambia", + phoneCode: "+220", + }, + { + countryCode: "ge", + country: "Georgia", + phoneCode: "+995", + }, + { + countryCode: "de", + country: "Germany", + phoneCode: "+49", + }, + { + countryCode: "gh", + country: "Ghana", + phoneCode: "+233", + }, + { + countryCode: "gi", + country: "Gibraltar", + phoneCode: "+350", + }, + { + countryCode: "gr", + country: "Greece", + phoneCode: "+30", + }, + { + countryCode: "gl", + country: "Greenland", + phoneCode: "+299", + }, + { + countryCode: "gd", + country: "Grenada", + phoneCode: "+1", + }, + { + countryCode: "gp", + country: "Guadeloupe", + phoneCode: "+590", + }, + { + countryCode: "gu", + country: "Guam", + phoneCode: "+1", + }, + { + countryCode: "gt", + country: "Guatemala", + phoneCode: "+502", + }, + { + countryCode: "gg", + country: "Guernsey", + phoneCode: "+44", + }, + { + countryCode: "gn", + country: "Guinea", + phoneCode: "+224", + }, + { + countryCode: "gw", + country: "Guinea-Bissau", + phoneCode: "+245", + }, + { + countryCode: "gy", + country: "Guyana", + phoneCode: "+592", + }, + { + countryCode: "ht", + country: "Haiti", + phoneCode: "+509", + }, + { + countryCode: "hn", + country: "Honduras", + phoneCode: "+504", + }, + { + countryCode: "hk", + country: "Hong Kong SAR China", + phoneCode: "+852", + }, + { + countryCode: "hu", + country: "Hungary", + phoneCode: "+36", + }, + { + countryCode: "is", + country: "Iceland", + phoneCode: "+354", + }, + { + countryCode: "in", + country: "India", + phoneCode: "+91", + }, + { + countryCode: "id", + country: "Indonesia", + phoneCode: "+62", + }, + { + countryCode: "ir", + country: "Iran", + phoneCode: "+98", + }, + { + countryCode: "iq", + country: "Iraq", + phoneCode: "+964", + }, + { + countryCode: "ie", + country: "Ireland", + phoneCode: "+353", + }, + { + countryCode: "im", + country: "Isle of Man", + phoneCode: "+44", + }, + { + countryCode: "il", + country: "Israel", + phoneCode: "+972", + }, + { + countryCode: "it", + country: "Italy", + phoneCode: "+39", + }, + { + countryCode: "jm", + country: "Jamaica", + phoneCode: "+1", + }, + { + countryCode: "jp", + country: "Japan", + phoneCode: "+81", + }, + { + countryCode: "je", + country: "Jersey", + phoneCode: "+44", + }, + { + countryCode: "jo", + country: "Jordan", + phoneCode: "+962", + }, + { + countryCode: "kz", + country: "Kazakhstan", + phoneCode: "+7", + }, + { + countryCode: "ke", + country: "Kenya", + phoneCode: "+254", + }, + { + countryCode: "ki", + country: "Kiribati", + phoneCode: "+686", + }, + { + countryCode: "xk", + country: "Kosovo", + phoneCode: "+383", + }, + { + countryCode: "kw", + country: "Kuwait", + phoneCode: "+965", + }, + { + countryCode: "kg", + country: "Kyrgyzstan", + phoneCode: "+996", + }, + { + countryCode: "la", + country: "Laos", + phoneCode: "+856", + }, + { + countryCode: "lv", + country: "Latvia", + phoneCode: "+371", + }, + { + countryCode: "lb", + country: "Lebanon", + phoneCode: "+961", + }, + { + countryCode: "ls", + country: "Lesotho", + phoneCode: "+266", + }, + { + countryCode: "lr", + country: "Liberia", + phoneCode: "+231", + }, + { + countryCode: "ly", + country: "Libya", + phoneCode: "+218", + }, + { + countryCode: "li", + country: "Liechtenstein", + phoneCode: "+423", + }, + { + countryCode: "lt", + country: "Lithuania", + phoneCode: "+370", + }, + { + countryCode: "lu", + country: "Luxembourg", + phoneCode: "+352", + }, + { + countryCode: "mo", + country: "Macau SAR China", + phoneCode: "+853", + }, + { + countryCode: "mk", + country: "Macedonia", + phoneCode: "+389", + }, + { + countryCode: "mg", + country: "Madagascar", + phoneCode: "+261", + }, + { + countryCode: "mw", + country: "Malawi", + phoneCode: "+265", + }, + { + countryCode: "my", + country: "Malaysia", + phoneCode: "+60", + }, + { + countryCode: "mv", + country: "Maldives", + phoneCode: "+960", + }, + { + countryCode: "ml", + country: "Mali", + phoneCode: "+223", + }, + { + countryCode: "mt", + country: "Malta", + phoneCode: "+356", + }, + { + countryCode: "mh", + country: "Marshall Islands", + phoneCode: "+692", + }, + { + countryCode: "mq", + country: "Martinique", + phoneCode: "+596", + }, + { + countryCode: "mr", + country: "Mauritania", + phoneCode: "+222", + }, + { + countryCode: "mu", + country: "Mauritius", + phoneCode: "+230", + }, + { + countryCode: "yt", + country: "Mayotte", + phoneCode: "+262", + }, + { + countryCode: "mx", + country: "Mexico", + phoneCode: "+52", + }, + { + countryCode: "fm", + country: "Micronesia", + phoneCode: "+691", + }, + { + countryCode: "md", + country: "Moldova", + phoneCode: "+373", + }, + { + countryCode: "mc", + country: "Monaco", + phoneCode: "+377", + }, + { + countryCode: "mn", + country: "Mongolia", + phoneCode: "+976", + }, + { + countryCode: "me", + country: "Montenegro", + phoneCode: "+382", + }, + { + countryCode: "ms", + country: "Montserrat", + phoneCode: "+1", + }, + { + countryCode: "ma", + country: "Morocco", + phoneCode: "+212", + }, + { + countryCode: "mz", + country: "Mozambique", + phoneCode: "+258", + }, + { + countryCode: "mm", + country: "Myanmar [Burma]", + phoneCode: "+95", + }, + { + countryCode: "na", + country: "Namibia", + phoneCode: "+264", + }, + { + countryCode: "nr", + country: "Nauru", + phoneCode: "+674", + }, + { + countryCode: "np", + country: "Nepal", + phoneCode: "+977", + }, + { + countryCode: "nl", + country: "Netherlands", + phoneCode: "+31", + }, + { + countryCode: "an", + country: "Netherlands Antilles", + phoneCode: "+599", + }, + { + countryCode: "nc", + country: "New Caledonia", + phoneCode: "+687", + }, + { + countryCode: "nz", + country: "New Zealand", + phoneCode: "+64", + }, + { + countryCode: "ni", + country: "Nicaragua", + phoneCode: "+505", + }, + { + countryCode: "ne", + country: "Niger", + phoneCode: "+227", + }, + { + countryCode: "ng", + country: "Nigeria", + phoneCode: "+234", + }, + { + countryCode: "nu", + country: "Niue", + phoneCode: "+683", + }, + { + countryCode: "nf", + country: "Norfolk Island", + phoneCode: "+672", + }, + { + countryCode: "kp", + country: "North Korea", + phoneCode: "+850", + }, + { + countryCode: "mp", + country: "Northern Mariana Islands", + phoneCode: "+1", + }, + { + countryCode: "no", + country: "Norway", + phoneCode: "+47", + }, + { + countryCode: "om", + country: "Oman", + phoneCode: "+968", + }, + { + countryCode: "pk", + country: "Pakistan", + phoneCode: "+92", + }, + { + countryCode: "pw", + country: "Palau", + phoneCode: "+680", + }, + { + countryCode: "ps", + country: "Palestinian Territories", + phoneCode: "+970", + }, + { + countryCode: "pa", + country: "Panama", + phoneCode: "+507", + }, + { + countryCode: "pg", + country: "Papua New Guinea", + phoneCode: "+675", + }, + { + countryCode: "py", + country: "Paraguay", + phoneCode: "+595", + }, + { + countryCode: "pe", + country: "Peru", + phoneCode: "+51", + }, + { + countryCode: "ph", + country: "Philippines", + phoneCode: "+63", + }, + { + countryCode: "pn", + country: "Pitcairn Islands", + phoneCode: "+64", + }, + { + countryCode: "pl", + country: "Poland", + phoneCode: "+48", + }, + { + countryCode: "pt", + country: "Portugal", + phoneCode: "+351", + }, + { + countryCode: "pr", + country: "Puerto Rico", + phoneCode: "+1", + }, + { + countryCode: "qa", + country: "Qatar", + phoneCode: "+974", + }, + { + countryCode: "re", + country: "Réunion", + phoneCode: "+262", + }, + { + countryCode: "ro", + country: "Romania", + phoneCode: "+40", + }, + { + countryCode: "ru", + country: "Russia", + phoneCode: "+7", + }, + { + countryCode: "rw", + country: "Rwanda", + phoneCode: "+250", + }, + { + countryCode: "bl", + country: "Saint Barthélemy", + phoneCode: "+590", + }, + { + countryCode: "sh", + country: "Saint Helena", + phoneCode: "+290", + }, + { + countryCode: "kn", + country: "Saint Kitts and Nevis", + phoneCode: "+1", + }, + { + countryCode: "lc", + country: "Saint Lucia", + phoneCode: "+1", + }, + { + countryCode: "mf", + country: "Saint Martin", + phoneCode: "+590", + }, + { + countryCode: "sx", + country: "Saint Martin", + phoneCode: "+1721", + }, + { + countryCode: "pm", + country: "Saint Pierre and Miquelon", + phoneCode: "+508", + }, + { + countryCode: "vc", + country: "Saint Vincent and the Grenadines", + phoneCode: "+1", + }, + { + countryCode: "ws", + country: "Samoa", + phoneCode: "+685", + }, + { + countryCode: "sm", + country: "San Marino", + phoneCode: "+378", + }, + { + countryCode: "st", + country: "São Tomé and Príncipe", + phoneCode: "+239", + }, + { + countryCode: "sa", + country: "Saudi Arabia", + phoneCode: "+966", + }, + { + countryCode: "sn", + country: "Senegal", + phoneCode: "+221", + }, + { + countryCode: "rs", + country: "Serbia", + phoneCode: "+381", + }, + { + countryCode: "sc", + country: "Seychelles", + phoneCode: "+248", + }, + { + countryCode: "sl", + country: "Sierra Leone", + phoneCode: "+232", + }, + { + countryCode: "sg", + country: "Singapore", + phoneCode: "+65", + }, + { + countryCode: "sk", + country: "Slovakia", + phoneCode: "+421", + }, + { + countryCode: "si", + country: "Slovenia", + phoneCode: "+386", + }, + { + countryCode: "sb", + country: "Solomon Islands", + phoneCode: "+677", + }, + { + countryCode: "so", + country: "Somalia", + phoneCode: "+252", + }, + { + countryCode: "za", + country: "South Africa", + phoneCode: "+27", + }, + { + countryCode: "gs", + country: "South Georgia and the South Sandwich Islands", + phoneCode: "+500", + }, + { + countryCode: "kr", + country: "South Korea", + phoneCode: "+82", + }, + { + countryCode: "ss", + country: "South Sudan", + phoneCode: "+211", + }, + { + countryCode: "es", + country: "Spain", + phoneCode: "+34", + }, + { + countryCode: "lk", + country: "Sri Lanka", + phoneCode: "+94", + }, + { + countryCode: "sd", + country: "Sudan", + phoneCode: "+249", + }, + { + countryCode: "sr", + country: "Suriname", + phoneCode: "+597", + }, + { + countryCode: "sj", + country: "Svalbard and Jan Mayen", + phoneCode: "+47", + }, + { + countryCode: "sz", + country: "Swaziland", + phoneCode: "+268", + }, + { + countryCode: "se", + country: "Sweden", + phoneCode: "+46", + }, + { + countryCode: "ch", + country: "Switzerland", + phoneCode: "+41", + }, + { + countryCode: "sy", + country: "Syria", + phoneCode: "+963", + }, + { + countryCode: "tw", + country: "Taiwan", + phoneCode: "+886", + }, + { + countryCode: "tj", + country: "Tajikistan", + phoneCode: "+992", + }, + { + countryCode: "tz", + country: "Tanzania", + phoneCode: "+255", + }, + { + countryCode: "th", + country: "Thailand", + phoneCode: "+66", + }, + { + countryCode: "tl", + country: "Timor-Leste", + phoneCode: "+670", + }, + { + countryCode: "tg", + country: "Togo", + phoneCode: "+228", + }, + { + countryCode: "tk", + country: "Tokelau", + phoneCode: "+690", + }, + { + countryCode: "to", + country: "Tonga", + phoneCode: "+676", + }, + { + countryCode: "tt", + country: "Trinidad and Tobago", + phoneCode: "+1", + }, + { + countryCode: "tn", + country: "Tunisia", + phoneCode: "+216", + }, + { + countryCode: "tr", + country: "Turkey", + phoneCode: "+90", + }, + { + countryCode: "tm", + country: "Turkmenistan", + phoneCode: "+993", + }, + { + countryCode: "tc", + country: "Turks and Caicos Islands", + phoneCode: "+1", + }, + { + countryCode: "tv", + country: "Tuvalu", + phoneCode: "+688", + }, + { + countryCode: "vi", + country: "U.S. Virgin Islands", + phoneCode: "+1", + }, + { + countryCode: "ug", + country: "Uganda", + phoneCode: "+256", + }, + { + countryCode: "ua", + country: "Ukraine", + phoneCode: "+380", + }, + { + countryCode: "ae", + country: "United Arab Emirates", + phoneCode: "+971", + }, + { + countryCode: "gb", + country: "United Kingdom", + phoneCode: "+44", + }, + { + countryCode: "us", + country: "United States", + phoneCode: "+1", + }, + { + countryCode: "uy", + country: "Uruguay", + phoneCode: "+598", + }, + { + countryCode: "uz", + country: "Uzbekistan", + phoneCode: "+998", + }, + { + countryCode: "vu", + country: "Vanuatu", + phoneCode: "+678", + }, + { + countryCode: "va", + country: "Vatican City", + phoneCode: "+379", + }, + { + countryCode: "ve", + country: "Venezuela", + phoneCode: "+58", + }, + { + countryCode: "vn", + country: "Vietnam", + phoneCode: "+84", + }, + { + countryCode: "wf", + country: "Wallis and Futuna", + phoneCode: "+681", + }, + { + countryCode: "eh", + country: "Western Sahara", + phoneCode: "+212", + }, + { + countryCode: "ye", + country: "Yemen", + phoneCode: "+967", + }, + { + countryCode: "zm", + country: "Zambia", + phoneCode: "+260", + }, + { + countryCode: "zw", + country: "Zimbabwe", + phoneCode: "+263", + }, +]; diff --git a/packages/logic/core/date.utils.ts b/packages/logic/core/date.utils.ts new file mode 100644 index 0000000..15bed65 --- /dev/null +++ b/packages/logic/core/date.utils.ts @@ -0,0 +1,83 @@ +import type { CalendarDate } from "@internationalized/date"; + +export function formatDuration(ms: number): string { + const seconds = Math.floor(ms / 1000); + if (seconds < 60) return `${seconds}s`; + + const minutes = Math.floor(seconds / 60); + const remainingSeconds = seconds % 60; + return `${minutes}m ${remainingSeconds}s`; +} + +export function formatDateTimeFromIsoString(isoString: string): string { + try { + const date = new Date(isoString); + return new Intl.DateTimeFormat("en-US", { + dateStyle: "medium", + timeStyle: "short", + }).format(date); + } catch (e) { + return "Invalid date"; + } +} + +export function getJustDateString(d: Date): string { + return d.toISOString().split("T")[0]; +} + +export function formatDateTime(dateTimeStr: string) { + const date = new Date(dateTimeStr); + return { + time: date.toLocaleTimeString("en-US", { + hour: "2-digit", + minute: "2-digit", + hour12: false, + }), + date: date.toLocaleDateString("en-US", { + weekday: "short", + day: "2-digit", + month: "short", + }), + }; +} + +export function formatDate(dateStr: string) { + return new Date(dateStr).toLocaleDateString("en-US", { + weekday: "short", + day: "2-digit", + month: "short", + }); +} + +export function isTimestampMoreThan1MinAgo(ts: string): boolean { + const lastPingedDate = new Date(ts); + const now = new Date(); + const diff = now.getTime() - lastPingedDate.getTime(); + return diff > 60000; +} + +export function isTimestampOlderThan(ts: string, seconds: number): boolean { + const lastPingedDate = new Date(ts); + const now = new Date(); + const diff = now.getTime() - lastPingedDate.getTime(); + return diff > seconds * 1000; +} + +export function makeDateStringISO(ds: string): string { + if (ds.includes("T")) { + return `${ds.split("T")[0]}T00:00:00.000Z`; + } + return `${ds}T00:00:00.000Z`; +} + +export function parseCalDateToDateString(v: CalendarDate) { + let month: string | number = v.month; + if (month < 10) { + month = `0${month}`; + } + let day: string | number = v.day; + if (day < 10) { + day = `0${day}`; + } + return `${v.year}-${month}-${day}`; +} diff --git a/packages/logic/core/error.ts b/packages/logic/core/error.ts new file mode 100644 index 0000000..83d8c49 --- /dev/null +++ b/packages/logic/core/error.ts @@ -0,0 +1,8 @@ +export type Err = { + code: string; + message: string; + description: string; + detail: string; + actionable?: boolean; + error?: any; +}; diff --git a/packages/logic/core/flow.execution.context.ts b/packages/logic/core/flow.execution.context.ts new file mode 100644 index 0000000..6c84e29 --- /dev/null +++ b/packages/logic/core/flow.execution.context.ts @@ -0,0 +1,5 @@ +export type FlowExecCtx = { + flowId: string; + userId?: string; + sessionId?: string; +}; diff --git a/packages/logic/core/hash.utils.ts b/packages/logic/core/hash.utils.ts new file mode 100644 index 0000000..b0da8fa --- /dev/null +++ b/packages/logic/core/hash.utils.ts @@ -0,0 +1,31 @@ +import { argon2id, hash as argonHash, verify as argonVerify } from "argon2"; + +export async function hashString(target: string): Promise { + const salt = Buffer.from(crypto.getRandomValues(new Uint8Array(16))).toString( + "hex", + ); + const hash = await argonHash(target, { + type: argon2id, + salt: Buffer.from(salt, "hex"), + hashLength: 32, + timeCost: 3, + memoryCost: 65536, + parallelism: 1, + }); + return hash; +} + +export async function verifyHash({ + hash, + target, +}: { + hash: string; + target: string; +}): Promise { + try { + const isValid = await argonVerify(hash, `${target}`); + return isValid; + } catch (err) { + return false; + } +} diff --git a/packages/logic/core/hono.helpers.ts b/packages/logic/core/hono.helpers.ts new file mode 100644 index 0000000..fd229c9 --- /dev/null +++ b/packages/logic/core/hono.helpers.ts @@ -0,0 +1,9 @@ +import type { Session, User } from "@/domains/user/data"; +import { FlowExecCtx } from "./flow.execution.context"; +import { Env } from "hono"; + +export interface HonoContext extends Env { + Bindings: { + locals: { user: User; session: Session; fCtx: FlowExecCtx }; + }; +} diff --git a/packages/logic/core/pagination.utils.ts b/packages/logic/core/pagination.utils.ts new file mode 100644 index 0000000..dd11b81 --- /dev/null +++ b/packages/logic/core/pagination.utils.ts @@ -0,0 +1,12 @@ +import * as v from "valibot"; + +export const paginationModel = v.object({ + cursor: v.optional(v.string()), + limit: v.pipe(v.number(), v.integer(), v.maxValue(100)), + asc: v.optional(v.boolean(), true), + totalItemCount: v.optional(v.pipe(v.number(), v.integer()), 0), + totalPages: v.pipe(v.number(), v.integer()), + page: v.pipe(v.number(), v.integer()), +}); + +export type PaginationModel = v.InferOutput; diff --git a/packages/logic/core/rate.limiter.ts b/packages/logic/core/rate.limiter.ts new file mode 100644 index 0000000..5f8b3bb --- /dev/null +++ b/packages/logic/core/rate.limiter.ts @@ -0,0 +1,40 @@ +import { logger } from "@pkg/logger"; + +export class RateLimiter { + private requestTimestamps: number[] = []; + private readonly callsPerMinute: number; + + constructor(callsPerMinute: number = 60) { + this.callsPerMinute = Math.min(callsPerMinute, 60); + } + + async checkRateLimit(): Promise { + const currentTime = Date.now(); + const oneMinuteAgo = currentTime - 60000; // 60 seconds in milliseconds + + // Remove timestamps older than 1 minute + this.requestTimestamps = this.requestTimestamps.filter( + (timestamp) => timestamp > oneMinuteAgo, + ); + + // If we're approaching the limit, wait until we have capacity + if (this.requestTimestamps.length >= this.callsPerMinute) { + const oldestRequest = this.requestTimestamps[0]; + const waitTime = oldestRequest + 60000 - currentTime; + + if (waitTime > 0) { + logger.warn( + `Rate limit approaching (${this.requestTimestamps.length} requests in last minute). Sleeping for ${waitTime}ms`, + ); + await new Promise((resolve) => setTimeout(resolve, waitTime)); + // After waiting, some timestamps may have expired + this.requestTimestamps = this.requestTimestamps.filter( + (timestamp) => timestamp > Date.now() - 60000, + ); + } + } + + // Add current request to timestamps + this.requestTimestamps.push(Date.now()); + } +} diff --git a/packages/logic/core/settings.ts b/packages/logic/core/settings.ts new file mode 100644 index 0000000..33b2d76 --- /dev/null +++ b/packages/logic/core/settings.ts @@ -0,0 +1 @@ +export { getSetting, settings } from "@pkg/settings"; diff --git a/packages/logic/core/string.utils/index.ts b/packages/logic/core/string.utils/index.ts new file mode 100644 index 0000000..7531a50 --- /dev/null +++ b/packages/logic/core/string.utils/index.ts @@ -0,0 +1,106 @@ +import * as v from "valibot"; + +export function capitalize(input: string, firstOfAllWords?: boolean): string { + // capitalize first letter of input + if (!firstOfAllWords) { + return input.charAt(0).toUpperCase() + input.slice(1); + } + let out = ""; + for (const word of input.split(" ")) { + out += word.charAt(0).toUpperCase() + word.slice(1) + " "; + } + return out.slice(0, -1); +} + +export function camelToSpacedPascal(input: string): string { + let result = ""; + let previousChar = ""; + for (const char of input) { + if (char === char.toUpperCase() && previousChar !== " ") { + result += " "; + } + result += char; + previousChar = char; + } + return result.charAt(0).toUpperCase() + result.slice(1); +} + +export function snakeToCamel(input: string): string { + if (!input) { + return input; + } + // also account for numbers and kebab-case + const splits = input.split(/[-_]/); + let result = splits[0]; + for (const split of splits.slice(1)) { + result += capitalize(split, true); + } + return result ?? ""; +} + +export function snakeToSpacedPascal(input: string): string { + return camelToSpacedPascal(snakeToCamel(input)); +} + +export function spacedPascalToSnake(input: string): string { + return input.split(" ").join("_").toLowerCase(); +} + +export function convertDashedLowerToTitleCase(input: string): string { + return input + .split("-") + .map( + (word) => + word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(), + ) + .join(" "); // Join the words with a space +} + +export function encodeCursor(cursor: T): string { + try { + // Convert the object to a JSON string + const jsonString = JSON.stringify(cursor); + // Convert to UTF-8 bytes, then base64 + return btoa( + encodeURIComponent(jsonString).replace(/%([0-9A-F]{2})/g, (_, p1) => + String.fromCharCode(parseInt(p1, 16)), + ), + ); + } catch (error) { + console.error("Error encoding cursor:", error); + throw new Error("Failed to encode cursor"); + } +} + +export function decodeCursor( + cursor: string, + parser: v.BaseSchema, +) { + try { + // Decode base64 back to UTF-8 string + const decoded = decodeURIComponent( + Array.prototype.map + .call(atob(cursor), (c) => { + return ( + "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2) + ); + }) + .join(""), + ); + // Parse back to object + const parsedData = JSON.parse(decoded); + const result = v.safeParse(parser, parsedData); + return result.success + ? { success: true, data: result.output as T } + : { + success: false, + error: new Error( + result.issues.map((i) => i.message).join(", "), + ), + data: undefined, + }; + } catch (error) { + console.error("Error decoding cursor:", error); + return { error: new Error("Failed to decode cursor"), data: undefined }; + } +} diff --git a/packages/logic/core/string.utils/sequence.matcher.ts b/packages/logic/core/string.utils/sequence.matcher.ts new file mode 100644 index 0000000..1196510 --- /dev/null +++ b/packages/logic/core/string.utils/sequence.matcher.ts @@ -0,0 +1,555 @@ +/** + * Similar to Python's difflib.SequenceMatcher + * + * A flexible class for comparing pairs of sequences of any type. + * Uses the Ratcliff-Obershelp algorithm with "gestalt pattern matching" + * to find the longest contiguous matching subsequences. + */ + +export interface Match { + /** Starting position in sequence a */ + a: number; + /** Starting position in sequence b */ + b: number; + /** Length of the matching block */ + size: number; +} + +export type OpCode = "replace" | "delete" | "insert" | "equal"; + +export interface OpCodeTuple { + /** Operation type */ + tag: OpCode; + /** Start index in sequence a */ + i1: number; + /** End index in sequence a */ + i2: number; + /** Start index in sequence b */ + j1: number; + /** End index in sequence b */ + j2: number; +} + +export type JunkFunction = (element: T) => boolean; + +export class SequenceMatcher { + private isjunk: JunkFunction | null; + private a: T[]; + private b: T[]; + private autojunk: boolean; + + // Cached data structures for sequence b + private bjunk: Set; + private bpopular: Set; + private b2j: Map; + + // Cached results + private fullbcount: Map | null = null; + private matchingBlocks: Match[] | null = null; + private opcodes: OpCodeTuple[] | null = null; + + constructor( + isjunk: JunkFunction | null = null, + a: T[] = [], + b: T[] = [], + autojunk: boolean = true, + ) { + this.isjunk = isjunk; + this.a = []; + this.b = []; + this.autojunk = autojunk; + this.bjunk = new Set(); + this.bpopular = new Set(); + this.b2j = new Map(); + + this.setSeqs(a, b); + } + + /** + * Set both sequences to be compared + */ + setSeqs(a: T[], b: T[]): void { + this.setSeq1(a); + this.setSeq2(b); + } + + /** + * Set the first sequence to be compared + */ + setSeq1(a: T[]): void { + if (a === this.a) return; + this.a = [...a]; + this.matchingBlocks = null; + this.opcodes = null; + } + + /** + * Set the second sequence to be compared + */ + setSeq2(b: T[]): void { + if (b === this.b) return; + this.b = [...b]; + this.matchingBlocks = null; + this.opcodes = null; + this.fullbcount = null; + this.chainB(); + } + + /** + * Analyze sequence b and build lookup structures + */ + private chainB(): void { + const b = this.b; + this.bjunk = new Set(); + this.bpopular = new Set(); + this.b2j = new Map(); + + // Count occurrences of each element + const elementCounts = new Map(); + for (const element of b) { + elementCounts.set(element, (elementCounts.get(element) || 0) + 1); + } + + // Determine junk and popular elements + const n = b.length; + const popularThreshold = Math.floor(n / 100) + 1; // > 1% of sequence + + for (const [element, count] of elementCounts) { + if (this.isjunk && this.isjunk(element)) { + this.bjunk.add(element); + } else if (this.autojunk && n >= 200 && count > popularThreshold) { + this.bpopular.add(element); + } + } + + // Build position mapping for non-junk, non-popular elements + for (let i = 0; i < b.length; i++) { + const element = b[i]; + if (!this.bjunk.has(element) && !this.bpopular.has(element)) { + if (!this.b2j.has(element)) { + this.b2j.set(element, []); + } + this.b2j.get(element)!.push(i); + } + } + } + + /** + * Find the longest matching block in a[alo:ahi] and b[blo:bhi] + */ + findLongestMatch( + alo: number = 0, + ahi: number | null = null, + blo: number = 0, + bhi: number | null = null, + ): Match { + if (ahi === null) ahi = this.a.length; + if (bhi === null) bhi = this.b.length; + + let besti = alo; + let bestj = blo; + let bestsize = 0; + + // Find all positions where a[i] appears in b + const j2len = new Map(); + + for (let i = alo; i < ahi; i++) { + const element = this.a[i]; + const positions = this.b2j.get(element) || []; + const newj2len = new Map(); + + for (const j of positions) { + if (j < blo) continue; + if (j >= bhi) break; + + const prevLen = j2len.get(j - 1) || 0; + const k = prevLen + 1; + newj2len.set(j, k); + + if (k > bestsize) { + besti = i - k + 1; + bestj = j - k + 1; + bestsize = k; + } + } + + j2len.clear(); + for (const [key, value] of newj2len) { + j2len.set(key, value); + } + } + + // Extend match with junk elements + while ( + besti > alo && + bestj > blo && + !this.isBJunk(this.b[bestj - 1]) && + this.elementsEqual(this.a[besti - 1], this.b[bestj - 1]) + ) { + besti--; + bestj--; + bestsize++; + } + + while ( + besti + bestsize < ahi && + bestj + bestsize < bhi && + !this.isBJunk(this.b[bestj + bestsize]) && + this.elementsEqual(this.a[besti + bestsize], this.b[bestj + bestsize]) + ) { + bestsize++; + } + + // Extend match with junk elements at the beginning + while (besti > alo && bestj > blo && this.isBJunk(this.b[bestj - 1])) { + besti--; + bestj--; + bestsize++; + } + + // Extend match with junk elements at the end + while ( + besti + bestsize < ahi && + bestj + bestsize < bhi && + this.isBJunk(this.b[bestj + bestsize]) + ) { + bestsize++; + } + + return { a: besti, b: bestj, size: bestsize }; + } + + /** + * Return list of non-overlapping matching blocks + */ + getMatchingBlocks(): Match[] { + if (this.matchingBlocks !== null) { + return this.matchingBlocks; + } + + const matches: Match[] = []; + this.getMatchingBlocksRecursive( + 0, + this.a.length, + 0, + this.b.length, + matches, + ); + + // Add sentinel + matches.push({ a: this.a.length, b: this.b.length, size: 0 }); + + this.matchingBlocks = matches; + return matches; + } + + /** + * Recursively find matching blocks + */ + private getMatchingBlocksRecursive( + alo: number, + ahi: number, + blo: number, + bhi: number, + matches: Match[], + ): void { + const match = this.findLongestMatch(alo, ahi, blo, bhi); + + if (match.size > 0) { + // Recurse on the pieces before and after the match + if (alo < match.a && blo < match.b) { + this.getMatchingBlocksRecursive( + alo, + match.a, + blo, + match.b, + matches, + ); + } + + matches.push(match); + + if (match.a + match.size < ahi && match.b + match.size < bhi) { + this.getMatchingBlocksRecursive( + match.a + match.size, + ahi, + match.b + match.size, + bhi, + matches, + ); + } + } + } + + /** + * Return list of 5-tuples describing how to turn a into b + */ + getOpcodes(): OpCodeTuple[] { + if (this.opcodes !== null) { + return this.opcodes; + } + + let i = 0; + let j = 0; + const opcodes: OpCodeTuple[] = []; + + for (const match of this.getMatchingBlocks()) { + let tag: OpCode = "equal"; + + if (i < match.a && j < match.b) { + tag = "replace"; + } else if (i < match.a) { + tag = "delete"; + } else if (j < match.b) { + tag = "insert"; + } + + if (tag !== "equal") { + opcodes.push({ + tag, + i1: i, + i2: match.a, + j1: j, + j2: match.b, + }); + } + + i = match.a + match.size; + j = match.b + match.size; + + // Don't add the sentinel match + if (match.size > 0) { + opcodes.push({ + tag: "equal", + i1: match.a, + i2: i, + j1: match.b, + j2: j, + }); + } + } + + this.opcodes = opcodes; + return opcodes; + } + + /** + * Return a measure of sequences' similarity (0.0-1.0) + */ + ratio(): number { + const matches = this.getMatchingBlocks() + .slice(0, -1) // Exclude sentinel + .reduce((sum, match) => sum + match.size, 0); + + const total = this.a.length + this.b.length; + return total === 0 ? 1.0 : (2.0 * matches) / total; + } + + /** + * Return an upper bound on ratio() relatively quickly + */ + quickRatio(): number { + if (this.fullbcount === null) { + this.fullbcount = new Map(); + for (const element of this.b) { + this.fullbcount.set( + element, + (this.fullbcount.get(element) || 0) + 1, + ); + } + } + + let matches = 0; + const tempCounts = new Map(this.fullbcount); + + for (const element of this.a) { + const count = tempCounts.get(element); + if (count && count > 0) { + matches++; + tempCounts.set(element, count - 1); + } + } + + const total = this.a.length + this.b.length; + return total === 0 ? 1.0 : (2.0 * matches) / total; + } + + /** + * Return an upper bound on ratio() very quickly + */ + realQuickRatio(): number { + const total = this.a.length + this.b.length; + return total === 0 + ? 1.0 + : (2.0 * Math.min(this.a.length, this.b.length)) / total; + } + + /** + * Check if element is junk in sequence b + */ + private isBJunk(element: T): boolean { + return this.bjunk.has(element); + } + + /** + * Check if two elements are equal + */ + private elementsEqual(a: T, b: T): boolean { + return a === b; + } +} + +/** + * Utility function to get close matches similar to Python's get_close_matches + */ +export function getCloseMatches( + word: T[], + possibilities: T[][], + n: number = 3, + cutoff: number = 0.6, +): T[][] { + if (n <= 0) { + throw new Error("n must be greater than 0"); + } + + const matches: Array<{ sequence: T[]; ratio: number }> = []; + + for (const possibility of possibilities) { + const matcher = new SequenceMatcher(null, word, possibility); + const ratio = matcher.ratio(); + + if (ratio >= cutoff) { + matches.push({ sequence: possibility, ratio }); + } + } + + // Sort by ratio (descending) and take top n + matches.sort((a, b) => b.ratio - a.ratio); + return matches.slice(0, n).map((match) => match.sequence); +} + +/** + * String-specific version of SequenceMatcher for character-by-character comparison. + * This class treats strings as sequences of characters while providing a string-friendly API. + */ +export class StringSequenceMatcher { + private matcher: SequenceMatcher; + + constructor( + isjunk: JunkFunction | null = null, + a: string = "", + b: string = "", + autojunk: boolean = true, + ) { + this.matcher = new SequenceMatcher( + isjunk, + Array.from(a), + Array.from(b), + autojunk, + ); + } + + /** + * Set both sequences to be compared + */ + setSeqs(a: string, b: string): void { + this.matcher.setSeqs(Array.from(a), Array.from(b)); + } + + /** + * Set the first sequence to be compared + */ + setSeq1(a: string): void { + this.matcher.setSeq1(Array.from(a)); + } + + /** + * Set the second sequence to be compared + */ + setSeq2(b: string): void { + this.matcher.setSeq2(Array.from(b)); + } + + /** + * Find the longest matching block in a[alo:ahi] and b[blo:bhi] + */ + findLongestMatch( + alo: number = 0, + ahi: number | null = null, + blo: number = 0, + bhi: number | null = null, + ): Match { + return this.matcher.findLongestMatch(alo, ahi, blo, bhi); + } + + /** + * Return list of non-overlapping matching blocks + */ + getMatchingBlocks(): Match[] { + return this.matcher.getMatchingBlocks(); + } + + /** + * Return list of 5-tuples describing how to turn a into b + */ + getOpcodes(): OpCodeTuple[] { + return this.matcher.getOpcodes(); + } + + /** + * Return a measure of sequences' similarity (0.0-1.0) + */ + ratio(): number { + return this.matcher.ratio(); + } + + /** + * Return an upper bound on ratio() relatively quickly + */ + quickRatio(): number { + return this.matcher.quickRatio(); + } + + /** + * Return an upper bound on ratio() very quickly + */ + realQuickRatio(): number { + return this.matcher.realQuickRatio(); + } +} + +/** + * Utility function for string similarity + */ +export function getStringSimilarity(a: string, b: string): number { + const matcher = new StringSequenceMatcher(null, a, b); + return matcher.ratio(); +} + +/** + * Get close string matches + */ +export function getCloseStringMatches( + word: string, + possibilities: string[], + n: number = 3, + cutoff: number = 0.6, +): string[] { + if (n <= 0) { + throw new Error("n must be greater than 0"); + } + + const matches: Array<{ string: string; ratio: number }> = []; + + for (const possibility of possibilities) { + const ratio = getStringSimilarity(word, possibility); + + if (ratio >= cutoff) { + matches.push({ string: possibility, ratio }); + } + } + + // Sort by ratio (descending) and take top n + matches.sort((a, b) => b.ratio - a.ratio); + return matches.slice(0, n).map((match) => match.string); +} diff --git a/packages/logic/domains/2fa/controller.ts b/packages/logic/domains/2fa/controller.ts new file mode 100644 index 0000000..191482f --- /dev/null +++ b/packages/logic/domains/2fa/controller.ts @@ -0,0 +1,349 @@ +import { errAsync, okAsync, ResultAsync } from "neverthrow"; +import { FlowExecCtx } from "@core/flow.execution.context"; +import { UserRepository } from "@domains/user/repository"; +import { getRedisInstance, Redis } from "@pkg/redis"; +import { TwofaRepository } from "./repository"; +import { auth } from "../auth/config.base"; +import type { TwoFaSession } from "./data"; +import { User } from "@domains/user/data"; +import { settings } from "@core/settings"; +import { type Err } from "@pkg/result"; +import { twofaErrors } from "./errors"; +import { logger } from "@pkg/logger"; +import { db } from "@pkg/db"; + +export class TwofaController { + constructor( + private twofaRepo: TwofaRepository, + private userRepo: UserRepository, + private store: Redis, + ) {} + + checkTotp(secret: string, code: string) { + return this.twofaRepo.checkTotp(secret, code); + } + + is2faEnabled(fctx: FlowExecCtx, userId: string) { + return this.twofaRepo + .getUsers2FAInfo(fctx, userId, true) + .map((data) => !!data) + .orElse(() => okAsync(false)); + } + + isUserBanned(fctx: FlowExecCtx, userId: string) { + return this.userRepo.isUserBanned(fctx, userId).orElse((error) => { + logger.error("Error checking user ban status:", error); + return okAsync(false); + }); + } + + setup2FA(fctx: FlowExecCtx, user: User) { + return this.is2faEnabled(fctx, user.id) + .andThen((enabled) => + enabled + ? errAsync(twofaErrors.alreadyEnabled(fctx)) + : this.twofaRepo.setup(fctx, user.id), + ) + .map((secret) => { + const appName = settings.appName; + const totpUri = `otpauth://totp/${appName}:${user.email}?secret=${secret}&issuer=${appName}`; + return { totpURI: totpUri, secret }; + }); + } + + verifyAndEnable2FA( + fctx: FlowExecCtx, + user: User, + code: string, + headers: Headers, + ) { + return this.is2faEnabled(fctx, user.id) + .andThen((enabled) => { + if (enabled) { + return errAsync(twofaErrors.alreadyEnabled(fctx)); + } + return okAsync(undefined); + }) + .andThen(() => { + logger.info(`Verifying 2fa for ${user.id} : ${code}`, { + flowId: fctx.flowId, + }); + return this.twofaRepo.verifyAndEnable2FA(fctx, user.id, code); + }) + .andThen((verified) => { + if (verified) { + return ResultAsync.combine([ + ResultAsync.fromPromise( + auth.api.revokeOtherSessions({ headers }), + () => twofaErrors.revokeSessionsFailed(fctx), + ), + this.userRepo.updateLastVerified2FaAtToNow( + fctx, + user.id, + ), + ]).map(() => true); + } + return okAsync(verified); + }); + } + + disable(fctx: FlowExecCtx, user: User, code: string) { + return this.is2faEnabled(fctx, user.id) + .andThen((enabled) => { + if (!enabled) { + return errAsync(twofaErrors.notEnabled(fctx)); + } + return okAsync(undefined); + }) + .andThen(() => this.twofaRepo.get2FASecret(fctx, user.id)) + .andThen((secret) => { + if (!secret) { + return errAsync(twofaErrors.invalidSetup(fctx)); + } + if (!this.checkTotp(secret, code)) { + return errAsync(twofaErrors.invalidCode(fctx)); + } + return okAsync(undefined); + }) + .andThen(() => this.twofaRepo.disable(fctx, user.id)); + } + + generateBackupCodes(fctx: FlowExecCtx, user: User) { + return this.is2faEnabled(fctx, user.id) + .andThen((enabled) => { + if (!enabled) { + return errAsync(twofaErrors.notEnabled(fctx)); + } + return okAsync(undefined); + }) + .andThen(() => this.twofaRepo.generateBackupCodes(fctx, user.id)); + } + + requiresInitialVerification( + fctx: FlowExecCtx, + user: User, + sessionId: string, + ) { + return this.is2faEnabled(fctx, user.id).andThen((enabled) => { + if (!enabled) { + return okAsync(false); + } + + return ResultAsync.fromPromise( + this.store.get(`initial_2fa_completed:${sessionId}`), + () => null, + ) + .map((completed) => !completed && completed !== "0") + .orElse(() => okAsync(true)); + }); + } + + requiresSensitiveActionVerification(fctx: FlowExecCtx, user: User) { + return this.is2faEnabled(fctx, user.id).andThen((enabled) => { + if (!enabled) { + return okAsync(false); + } + + if (!user.last2FAVerifiedAt) { + return okAsync(true); + } + + const requiredHours = settings.twofaRequiredHours || 24; + const verificationAge = + Date.now() - user.last2FAVerifiedAt.getTime(); + const maxAge = requiredHours * 60 * 60 * 1000; + + return okAsync(verificationAge > maxAge); + }); + } + + markInitialVerificationComplete(sessionId: string) { + return ResultAsync.fromPromise( + this.store.setex( + `initial_2fa_completed:${sessionId}`, + 60 * 60 * 24 * 7, + "true", + ), + () => null, + ) + .map(() => undefined) + .orElse((error) => { + logger.error("Error marking initial 2FA as complete:", error); + return okAsync(undefined); + }); + } + + startVerification( + fctx: FlowExecCtx, + params: { + userId: string; + sessionId: string; + ipAddress?: string; + userAgent?: string; + }, + ) { + return this.twofaRepo.createSession(fctx, params).map((session) => ({ + verificationToken: session.verificationToken, + })); + } + + private validateSession(fctx: FlowExecCtx, session: TwoFaSession) { + if (session.status !== "pending") { + return errAsync(twofaErrors.sessionNotActive(fctx)); + } + + if (session.expiresAt < new Date()) { + return this.twofaRepo + .updateSession(fctx, session.id, { status: "expired" }) + .andThen(() => errAsync(twofaErrors.sessionExpired(fctx))); + } + + return okAsync(session); + } + + private handleMaxAttempts( + fctx: FlowExecCtx, + session: TwoFaSession, + userId: string, + ) { + const banExpiresAt = new Date(); + banExpiresAt.setHours(banExpiresAt.getHours() + 1); + + return this.twofaRepo + .updateSession(fctx, session.id, { status: "failed" }) + .andThen(() => + this.userRepo.banUser( + fctx, + userId, + "Too many failed 2FA verification attempts", + banExpiresAt, + ), + ) + .andThen(() => errAsync(twofaErrors.tooManyAttempts(fctx))); + } + + private checkAttemptsLimit( + fctx: FlowExecCtx, + session: TwoFaSession, + userId: string, + ) { + if (session.attempts >= session.maxAttempts) { + return this.handleMaxAttempts(fctx, session, userId); + } + return okAsync(session); + } + + private checkCodeReplay( + fctx: FlowExecCtx, + session: TwoFaSession, + code: string, + ): ResultAsync { + if (session.codeUsed === code) { + return this.twofaRepo + .incrementAttempts(fctx, session.id) + .andThen(() => errAsync(twofaErrors.codeReplay(fctx))); + } + return okAsync(session); + } + + private verifyTotpCode( + fctx: FlowExecCtx, + session: TwoFaSession, + userId: string, + code: string, + ) { + return this.twofaRepo.get2FASecret(fctx, userId).andThen((secret) => { + if (!secret) { + return errAsync(twofaErrors.invalidSetup(fctx)); + } + + if (!this.checkTotp(secret, code)) { + return this.twofaRepo + .incrementAttempts(fctx, session.id) + .andThen(() => errAsync(twofaErrors.invalidCode(fctx))); + } + + return okAsync(session); + }); + } + + private completeVerification( + fctx: FlowExecCtx, + session: TwoFaSession, + userId: string, + code: string, + ) { + return this.twofaRepo + .updateSession(fctx, session.id, { + status: "verified", + verifiedAt: new Date(), + codeUsed: code, + }) + .andThen(() => + ResultAsync.combine([ + this.userRepo.updateLastVerified2FaAtToNow(fctx, userId), + this.markInitialVerificationComplete(session.sessionId), + ]), + ) + .map(() => undefined); + } + + verifyCode( + fctx: FlowExecCtx, + params: { verificationSessToken: string; code: string }, + user?: User, + ) { + if (!user) { + return errAsync(twofaErrors.userNotFound(fctx)); + } + + return this.is2faEnabled(fctx, user.id) + .andThen((enabled) => { + if (!enabled) { + return errAsync( + twofaErrors.notEnabledForVerification(fctx), + ); + } + return okAsync(undefined); + }) + .andThen(() => + this.twofaRepo.getSessionByToken( + fctx, + params.verificationSessToken, + ), + ) + .andThen((session) => { + if (!session) { + return errAsync(twofaErrors.sessionNotFound(fctx)); + } + return okAsync(session); + }) + .andThen((session) => this.validateSession(fctx, session)) + .andThen((session) => + this.checkAttemptsLimit(fctx, session, user.id), + ) + .andThen((session) => + this.checkCodeReplay(fctx, session, params.code), + ) + .andThen((session) => + this.verifyTotpCode(fctx, session, user.id, params.code), + ) + .andThen((session) => + this.completeVerification(fctx, session, user.id, params.code), + ) + .map(() => ({ success: true })); + } + + cleanupExpiredSessions(fctx: FlowExecCtx) { + return this.twofaRepo.cleanupExpiredSessions(fctx); + } +} + +export function getTwofaController() { + const _redis = getRedisInstance(); + return new TwofaController( + new TwofaRepository(db, _redis), + new UserRepository(db), + _redis, + ); +} diff --git a/packages/logic/domains/2fa/data.ts b/packages/logic/domains/2fa/data.ts new file mode 100644 index 0000000..7e19e0a --- /dev/null +++ b/packages/logic/domains/2fa/data.ts @@ -0,0 +1,48 @@ +import * as v from "valibot"; + +export const startVerificationSchema = v.object({ + userId: v.string(), + sessionId: v.string(), +}); + +export const verifyCodeSchema = v.object({ + verificationToken: v.string(), + code: v.string(), +}); + +export const enable2FACodeSchema = v.object({ + code: v.string(), +}); + +export const disable2FASchema = v.object({ + code: v.string(), +}); + +export const twoFactorSchema = v.object({ + id: v.string(), + secret: v.string(), + backupCodes: v.array(v.string()), + userId: v.string(), + createdAt: v.date(), + updatedAt: v.date(), +}); +export type TwoFactor = v.InferOutput; + +export type TwoFaSessionStatus = "pending" | "verified" | "failed" | "expired"; + +export const twoFaSessionSchema = v.object({ + id: v.string(), + userId: v.string(), + sessionId: v.string(), + verificationToken: v.string(), + codeUsed: v.optional(v.string()), + status: v.picklist(["pending", "verified", "failed", "expired"]), + attempts: v.number(), + maxAttempts: v.number(), + verifiedAt: v.optional(v.date()), + expiresAt: v.date(), + createdAt: v.date(), + ipAddress: v.string(), + userAgent: v.string(), +}); +export type TwoFaSession = v.InferOutput; diff --git a/packages/logic/domains/2fa/errors.ts b/packages/logic/domains/2fa/errors.ts new file mode 100644 index 0000000..5777acf --- /dev/null +++ b/packages/logic/domains/2fa/errors.ts @@ -0,0 +1,180 @@ +import { FlowExecCtx } from "@/core/flow.execution.context"; +import { ERROR_CODES, type Err } from "@pkg/result"; +import { getError } from "@pkg/logger"; + +export const twofaErrors = { + dbError: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Database operation failed", + description: "Please try again later", + detail, + }), + + alreadyEnabled: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.AUTH_ERROR, + message: "2FA already enabled", + description: "Disable it first if you want to re-enable it", + detail: "2FA already enabled", + }), + + notEnabled: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.AUTH_ERROR, + message: "2FA not enabled for this user", + description: "Enable 2FA to perform this action", + detail: "2FA not enabled for this user", + }), + + userNotFound: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.AUTH_ERROR, + message: "User not found", + description: "Session is invalid or expired", + detail: "User ID not found in database", + }), + + sessionNotActive: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.AUTH_ERROR, + message: "Verification session is no longer active", + description: "Please request a new verification code", + detail: "Session status is not 'pending'", + }), + + sessionExpired: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.AUTH_ERROR, + message: "Verification session has expired", + description: "Please request a new verification code", + detail: "Session expired timestamp passed", + }), + + sessionNotFound: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.NOT_FOUND, + message: "Invalid or expired verification session", + description: "Your verification session has expired or is invalid", + detail: "Session not found by verification token", + }), + + tooManyAttempts: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.BANNED, + message: "Too many failed attempts", + description: + "Your account has been banned, contact us to resolve this issue", + detail: "Max attempts reached for 2FA verification", + }), + + codeReplay: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.AUTH_ERROR, + message: "This code has already been used", + description: "Please request a new verification code", + detail: "Code replay attempt detected", + }), + + invalidSetup: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.AUTH_ERROR, + message: "Invalid 2FA setup found", + description: "Please contact us to resolve this issue", + detail: "Invalid 2FA data found", + }), + + invalidCode: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.AUTH_ERROR, + message: "Invalid verification code", + description: "Please try again with the correct code", + detail: "Code is invalid", + }), + + notEnabledForVerification: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.AUTH_ERROR, + message: "2FA not enabled for this user", + description: + "Two-factor authentication is not enabled on your account", + detail: "User has 2FA disabled but verification attempted", + }), + + revokeSessionsFailed: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.AUTH_ERROR, + message: "Failed to revoke sessions", + description: "Please try again later", + detail: "Failed to revoke other sessions", + }), + + // Repository errors + notFound: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.NOT_FOUND, + message: "2FA not found", + description: "Likely not enabled, otherwise please contact us :)", + detail: "2FA not found", + }), + + setupNotFound: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.VALIDATION_ERROR, + message: "Cannot perform action", + description: "If 2FA is not enabled, please refresh and try again", + detail: "2FA setup not found", + }), + + maxAttemptsReached: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.AUTH_ERROR, + message: "Too many failed attempts", + description: "Please refresh and try again", + detail: "Max attempts reached for session", + }), + + backupCodesNotFound: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.NOT_FOUND, + message: "2FA info not found", + description: "Please setup 2FA or contact us if this is unexpected", + detail: "2FA info not found for user", + }), + + backupCodesAlreadyGenerated: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.AUTH_ERROR, + message: "Backup codes already generated", + description: + "Can only generate if not already present, or all are used up", + detail: "Backup codes already generated", + }), + + sessionNotFoundById: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.NOT_FOUND, + message: "2FA session not found", + description: "The verification session may have expired", + detail: "Session ID not found in database", + }), +}; diff --git a/packages/logic/domains/2fa/repository.ts b/packages/logic/domains/2fa/repository.ts new file mode 100644 index 0000000..eb10b1e --- /dev/null +++ b/packages/logic/domains/2fa/repository.ts @@ -0,0 +1,554 @@ +import { errAsync, okAsync, ResultAsync } from "neverthrow"; +import { FlowExecCtx } from "@core/flow.execution.context"; +import { hashString, verifyHash } from "@/core/hash.utils"; +import { twoFactor, twofaSessions } from "@pkg/db/schema"; +import { TwoFactor, type TwoFaSession } from "./data"; +import { and, Database, eq, gt, lt } from "@pkg/db"; +import { settings } from "@core/settings"; +import type { Err } from "@pkg/result"; +import { twofaErrors } from "./errors"; +import { authenticator } from "otplib"; +import { logger } from "@pkg/logger"; +import { Redis } from "@pkg/redis"; +import { nanoid } from "nanoid"; + +type TwoFaSetup = { + secret: string; + lastUsedCode: string; + tries: number; +}; + +export class TwofaRepository { + private PENDING_KEY_PREFIX = "pending_enabling_2fa:"; + private EXPIRY_TIME = 60 * 20; // 20 mins + private DEFAULT_BACKUP_CODES_AMT = 8; + private MAX_SETUP_ATTEMPTS = 3; + + constructor( + private db: Database, + private store: Redis, + ) {} + + checkTotp(secret: string, code: string) { + const checked = authenticator.verify({ secret, token: code }); + logger.debug("TOTP check result", { checked }); + return checked; + } + + async checkBackupCode(hash: string, code: string) { + return verifyHash({ hash, target: code }); + } + + private getKey(userId: string) { + if (userId.includes(this.PENDING_KEY_PREFIX)) { + return userId; + } + return `${this.PENDING_KEY_PREFIX}${userId}`; + } + + getUsers2FAInfo( + fctx: FlowExecCtx, + userId: string, + returnUndefined?: boolean, + ): ResultAsync { + logger.info("Getting user 2FA info", { ...fctx, userId }); + + return ResultAsync.fromPromise( + this.db.query.twoFactor.findFirst({ + where: eq(twoFactor.userId, userId), + }), + () => twofaErrors.dbError(fctx, "Failed to query 2FA info"), + ).andThen((found) => { + if (!found) { + logger.debug("2FA info not found for user", { + ...fctx, + userId, + }); + if (returnUndefined) { + return okAsync(undefined); + } + return errAsync(twofaErrors.notFound(fctx)); + } + logger.info("2FA info retrieved successfully", { ...fctx, userId }); + return okAsync(found as TwoFactor); + }); + } + + isSetupPending( + fctx: FlowExecCtx, + userId: string, + ): ResultAsync { + logger.debug("Checking if 2FA setup is pending", { ...fctx, userId }); + + return ResultAsync.fromPromise( + this.store.get(this.getKey(userId)), + () => + twofaErrors.dbError( + fctx, + "Failed to check setup pending status", + ), + ).map((found) => { + const isPending = !!found; + logger.debug("Setup pending status checked", { + ...fctx, + userId, + isPending, + }); + return isPending; + }); + } + + setup(fctx: FlowExecCtx, userId: string): ResultAsync { + logger.info("Starting 2FA setup", { ...fctx, userId }); + + return ResultAsync.fromSafePromise( + (async () => { + const secret = authenticator.generateSecret(); + const payload = { + secret, + lastUsedCode: "", + tries: 0, + } as TwoFaSetup; + await this.store.setex( + this.getKey(userId), + this.EXPIRY_TIME, + JSON.stringify(payload), + ); + logger.info("Created temp 2FA session", { + ...fctx, + userId, + expiresIn: this.EXPIRY_TIME, + }); + return secret; + })(), + ).mapErr(() => + twofaErrors.dbError(fctx, "Setting to data store failed"), + ); + } + + verifyAndEnable2FA( + fctx: FlowExecCtx, + userId: string, + code: string, + ): ResultAsync { + logger.info("Verifying and enabling 2FA", { ...fctx, userId }); + + return ResultAsync.fromPromise( + this.store.get(this.getKey(userId)), + () => twofaErrors.dbError(fctx, "Failed to get setup session"), + ) + .andThen((payload) => { + if (!payload) { + logger.error("Setup session not found", { + ...fctx, + userId, + }); + return errAsync(twofaErrors.setupNotFound(fctx)); + } + return okAsync(JSON.parse(payload) as TwoFaSetup); + }) + .andThen((payloadObj) => { + const key = this.getKey(userId); + + if (payloadObj.tries >= this.MAX_SETUP_ATTEMPTS) { + logger.warn("Max setup attempts reached", { + ...fctx, + userId, + tries: payloadObj.tries, + }); + return ResultAsync.fromPromise(this.store.del(key), () => + twofaErrors.dbError( + fctx, + "Failed to delete setup session", + ), + ).andThen(() => + errAsync(twofaErrors.maxAttemptsReached(fctx)), + ); + } + + if ( + !this.checkTotp(payloadObj.secret, code) || + code === payloadObj.lastUsedCode + ) { + logger.warn("Invalid 2FA code during setup", { + ...fctx, + userId, + tries: payloadObj.tries + 1, + codeReused: code === payloadObj.lastUsedCode, + }); + return ResultAsync.fromPromise( + this.store.setex( + key, + this.EXPIRY_TIME, + JSON.stringify({ + secret: payloadObj.secret, + lastUsedCode: code, + tries: payloadObj.tries + 1, + }), + ), + () => + twofaErrors.dbError( + fctx, + "Failed to update setup session", + ), + ).map(() => false); + } + + logger.info("2FA code verified successfully, enabling 2FA", { + ...fctx, + userId, + }); + + return ResultAsync.fromPromise(this.store.del(key), () => + twofaErrors.dbError(fctx, "Failed to delete setup session"), + ) + .andThen(() => + ResultAsync.fromPromise( + this.db + .insert(twoFactor) + .values({ + id: nanoid(), + secret: payloadObj.secret, + userId: userId, + createdAt: new Date(), + updatedAt: new Date(), + }) + .execute(), + () => + twofaErrors.dbError( + fctx, + "Failed to insert 2FA record", + ), + ), + ) + .map(() => { + logger.info("2FA enabled successfully", { + ...fctx, + userId, + }); + return true; + }); + }); + } + + disable(fctx: FlowExecCtx, userId: string): ResultAsync { + logger.info("Disabling 2FA", { ...fctx, userId }); + + return ResultAsync.fromPromise( + this.db + .delete(twoFactor) + .where(eq(twoFactor.userId, userId)) + .execute(), + () => twofaErrors.dbError(fctx, "Failed to delete 2FA record"), + ).map((result) => { + logger.info("2FA disabled successfully", { ...fctx, userId }); + return true; + }); + } + + generateBackupCodes( + fctx: FlowExecCtx, + userId: string, + ): ResultAsync { + logger.info("Generating backup codes", { ...fctx, userId }); + + return ResultAsync.fromPromise( + this.db.query.twoFactor.findFirst({ + where: eq(twoFactor.userId, userId), + }), + () => twofaErrors.dbError(fctx, "Failed to query 2FA info"), + ) + .andThen((found) => { + if (!found) { + logger.error("2FA not enabled for user", { + ...fctx, + userId, + }); + return errAsync(twofaErrors.backupCodesNotFound(fctx)); + } + if (found.backupCodes && found.backupCodes.length) { + logger.warn("Backup codes already generated", { + ...fctx, + userId, + }); + return errAsync( + twofaErrors.backupCodesAlreadyGenerated(fctx), + ); + } + return okAsync(found); + }) + .andThen(() => { + const codes = Array.from( + { length: this.DEFAULT_BACKUP_CODES_AMT }, + () => nanoid(12), + ); + + logger.debug("Backup codes generated, hashing", { + ...fctx, + userId, + count: codes.length, + }); + + return ResultAsync.fromPromise( + (async () => { + const hashed = []; + for (const code of codes) { + const hash = await hashString(code); + hashed.push(hash); + } + return { codes, hashed }; + })(), + () => + twofaErrors.dbError( + fctx, + "Failed to hash backup codes", + ), + ).andThen(({ codes, hashed }) => + ResultAsync.fromPromise( + this.db + .update(twoFactor) + .set({ backupCodes: hashed }) + .where(eq(twoFactor.userId, userId)) + .returning(), + () => + twofaErrors.dbError( + fctx, + "Failed to update backup codes", + ), + ).map(() => { + logger.info("Backup codes generated successfully", { + ...fctx, + userId, + }); + return codes; + }), + ); + }); + } + + get2FASecret( + fctx: FlowExecCtx, + userId: string, + ): ResultAsync { + logger.debug("Getting 2FA secret", { ...fctx, userId }); + + return ResultAsync.fromPromise( + this.db + .select() + .from(twoFactor) + .where(eq(twoFactor.userId, userId)) + .limit(1), + () => twofaErrors.dbError(fctx, "Failed to query 2FA secret"), + ).map((result) => { + if (!result.length) { + logger.debug("No 2FA secret found", { ...fctx, userId }); + return null; + } + logger.debug("2FA secret retrieved", { ...fctx, userId }); + return result[0].secret; + }); + } + + createSession( + fctx: FlowExecCtx, + params: { + userId: string; + sessionId: string; + ipAddress?: string; + userAgent?: string; + }, + ): ResultAsync { + logger.info("Creating 2FA verification session", { + ...fctx, + userId: params.userId, + sessionId: params.sessionId, + }); + + return ResultAsync.fromSafePromise( + (async () => { + const expiryMinutes = settings.twofaSessionExpiryMinutes || 10; + const now = new Date(); + const expiresAt = new Date( + now.getTime() + expiryMinutes * 60 * 1000, + ); + + return { expiresAt, now, params }; + })(), + ).andThen(({ expiresAt, now, params }) => + ResultAsync.fromPromise( + this.db + .insert(twofaSessions) + .values({ + id: nanoid(), + userId: params.userId, + sessionId: params.sessionId, + verificationToken: nanoid(32), + status: "pending", + attempts: 0, + maxAttempts: 5, + expiresAt, + createdAt: now, + ipAddress: params.ipAddress, + userAgent: params.userAgent, + }) + .returning(), + () => twofaErrors.dbError(fctx, "Failed to create 2FA session"), + ).map(([session]) => { + logger.info("2FA verification session created", { + ...fctx, + sessionId: session.id, + userId: params.userId, + }); + return session as TwoFaSession; + }), + ); + } + + getSessionByToken( + fctx: FlowExecCtx, + token: string, + ): ResultAsync { + logger.debug("Getting 2FA session by token", { ...fctx }); + + return ResultAsync.fromPromise( + this.db + .select() + .from(twofaSessions) + .where( + and( + eq(twofaSessions.verificationToken, token), + gt(twofaSessions.expiresAt, new Date()), + ), + ) + .limit(1), + () => twofaErrors.dbError(fctx, "Failed to query 2FA session"), + ).map((result) => { + if (!result.length) { + logger.warn("2FA session not found or expired", { ...fctx }); + return null; + } + logger.debug("2FA session found", { + ...fctx, + sessionId: result[0].id, + }); + return result[0] as TwoFaSession; + }); + } + + updateSession( + fctx: FlowExecCtx, + id: string, + updates: Partial< + Pick< + TwoFaSession, + "status" | "attempts" | "verifiedAt" | "codeUsed" + > + >, + ): ResultAsync { + logger.debug("Updating 2FA session", { + ...fctx, + sessionId: id, + updates, + }); + + return ResultAsync.fromPromise( + this.db + .update(twofaSessions) + .set(updates) + .where(eq(twofaSessions.id, id)) + .returning(), + () => twofaErrors.dbError(fctx, "Failed to update 2FA session"), + ).andThen(([session]) => { + if (!session) { + logger.error("2FA session not found for update", { + ...fctx, + sessionId: id, + }); + return errAsync(twofaErrors.sessionNotFoundById(fctx)); + } + logger.debug("2FA session updated successfully", { + ...fctx, + sessionId: id, + }); + return okAsync(session as TwoFaSession); + }); + } + + incrementAttempts( + fctx: FlowExecCtx, + id: string, + ): ResultAsync { + logger.debug("Incrementing session attempts", { + ...fctx, + sessionId: id, + }); + + return ResultAsync.fromPromise( + this.db.query.twofaSessions.findFirst({ + where: eq(twofaSessions.id, id), + columns: { id: true, attempts: true }, + }), + () => + twofaErrors.dbError( + fctx, + "Failed to query session for increment", + ), + ) + .andThen((s) => { + if (!s) { + logger.error("Session not found for increment", { + ...fctx, + sessionId: id, + }); + return errAsync(twofaErrors.sessionNotFoundById(fctx)); + } + return okAsync(s); + }) + .andThen((s) => + ResultAsync.fromPromise( + this.db + .update(twofaSessions) + .set({ attempts: s.attempts + 1 }) + .where(eq(twofaSessions.id, id)) + .returning(), + () => + twofaErrors.dbError( + fctx, + "Failed to increment attempts", + ), + ).andThen(([session]) => { + if (!session) { + logger.error("Session not found after increment", { + ...fctx, + sessionId: id, + }); + return errAsync(twofaErrors.sessionNotFoundById(fctx)); + } + + logger.warn("Failed verification attempt", { + ...fctx, + sessionId: session.id, + attempts: session.attempts, + }); + + return okAsync(session as TwoFaSession); + }), + ); + } + + cleanupExpiredSessions(fctx: FlowExecCtx): ResultAsync { + logger.info("Cleaning up expired 2FA sessions", { ...fctx }); + + return ResultAsync.fromPromise( + this.db + .delete(twofaSessions) + .where(lt(twofaSessions.expiresAt, new Date())), + () => + twofaErrors.dbError(fctx, "Failed to cleanup expired sessions"), + ).map((result) => { + const count = result.rowCount || 0; + logger.info("Expired sessions cleaned up", { ...fctx, count }); + return count; + }); + } +} diff --git a/packages/logic/domains/2fa/router.ts b/packages/logic/domains/2fa/router.ts new file mode 100644 index 0000000..d682399 --- /dev/null +++ b/packages/logic/domains/2fa/router.ts @@ -0,0 +1,170 @@ +import { + disable2FASchema, + enable2FACodeSchema, + startVerificationSchema, + verifyCodeSchema, +} from "./data"; +import { sValidator } from "@hono/standard-validator"; +import { HonoContext } from "@/core/hono.helpers"; +import { getTwofaController } from "./controller"; +import { auth } from "@domains/auth/config.base"; +import { Hono } from "hono"; + +const twofaController = getTwofaController(); + +export const twofaRouter = new Hono() + .post("/setup", async (c) => { + const res = await twofaController.setup2FA( + c.env.locals.fCtx, + c.env.locals.user, + ); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }) + .post( + "/verify-and-enable", + sValidator("json", enable2FACodeSchema), + async (c) => { + const data = c.req.valid("json"); + const res = await twofaController.verifyAndEnable2FA( + c.env.locals.fCtx, + c.env.locals.user, + data.code, + c.req.raw.headers, + ); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }, + ) + .get("/generate-backup-codes", async (c) => { + const res = await twofaController.generateBackupCodes( + c.env.locals.fCtx, + c.env.locals.user, + ); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }) + .delete("/disable", sValidator("json", disable2FASchema), async (c) => { + const data = c.req.valid("json"); + const res = await twofaController.disable( + c.env.locals.fCtx, + c.env.locals.user, + data.code, + ); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }) + .get("/requires-verification", async (c) => { + const user = c.env.locals.user; + const sessionId = c.req.query("sessionId")?.toString() ?? ""; + const res = await twofaController.requiresInitialVerification( + c.env.locals.fCtx, + user, + sessionId, + ); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }) + .get("/requires-sensitive-action", async (c) => { + const res = await twofaController.requiresSensitiveActionVerification( + c.env.locals.fCtx, + c.env.locals.user, + ); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }) + .post( + "/start-verification-session", + sValidator("json", startVerificationSchema), + async (c) => { + const data = c.req.valid("json"); + + const ipAddress = + c.req.header("x-forwarded-for") || + c.req.header("x-real-ip") || + "unknown"; + const userAgent = c.req.header("user-agent") || "unknown"; + + const res = await twofaController.startVerification( + c.env.locals.fCtx, + { + userId: data.userId, + sessionId: data.sessionId, + ipAddress, + userAgent, + }, + ); + + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + ); + }, + ) + .post( + "/verify-session-code", + sValidator("json", verifyCodeSchema), + async (c) => { + const data = c.req.valid("json"); + + let user = c.env.locals.user; + if (!user) { + const out = await auth.api.getSession({ + headers: c.req.raw.headers, + }); + user = out?.user as any; + } + + const res = await twofaController.verifyCode( + c.env.locals.fCtx, + { + verificationSessToken: data.verificationToken, + code: data.code, + }, + user, + ); + + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }, + ) + .post("/cleanup-expired-sessions", async (c) => { + const res = await twofaController.cleanupExpiredSessions( + c.env.locals.fCtx, + ); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }); diff --git a/packages/logic/domains/2fa/sensitive-actions.ts b/packages/logic/domains/2fa/sensitive-actions.ts new file mode 100644 index 0000000..54d4315 --- /dev/null +++ b/packages/logic/domains/2fa/sensitive-actions.ts @@ -0,0 +1,43 @@ +import { FlowExecCtx } from "@core/flow.execution.context"; +import { getTwofaController } from "./controller"; +import type { User } from "@/domains/user/data"; + +const twofaController = getTwofaController(); + +/** + * Check if user needs 2FA verification for sensitive actions + * Call this before executing sensitive operations like: + * - Changing password + * - Viewing billing info + * - Deleting account + * - etc. + */ +export async function requiresSensitiveAction2FA( + fctx: FlowExecCtx, + user: User, +): Promise { + const result = await twofaController.requiresSensitiveActionVerification( + fctx, + user, + ); + return result.match( + (data) => data, + () => true, // On error, require verification for security + ); +} + +export async function checkInitial2FaRequired( + fctx: FlowExecCtx, + user: User, + sessionId: string, +): Promise { + const result = await twofaController.requiresInitialVerification( + fctx, + user, + sessionId, + ); + return result.match( + (data) => data, + () => true, + ); +} diff --git a/packages/logic/domains/auth/config.base.ts b/packages/logic/domains/auth/config.base.ts new file mode 100644 index 0000000..9bf35d2 --- /dev/null +++ b/packages/logic/domains/auth/config.base.ts @@ -0,0 +1,205 @@ +import { + admin, + customSession, + magicLink, + multiSession, + username, +} from "better-auth/plugins"; +import { getUserController, UserController } from "../user/controller"; +import { AuthController, getAuthController } from "./controller"; +import { drizzleAdapter } from "better-auth/adapters/drizzle"; +import { FlowExecCtx } from "@/core/flow.execution.context"; +import { UserRoleMap } from "@domains/user/data"; +import { getRedisInstance } from "@pkg/redis"; +import { APIError } from "better-auth/api"; +import { settings } from "@core/settings"; +import { betterAuth } from "better-auth"; +import { logger } from "@pkg/logger"; +import { db, schema } from "@pkg/db"; +import { nanoid } from "nanoid"; + +// Constants +const EMAIL_EXPIRES_IN_MINS = 10; +const EMAIL_EXPIRES_IN_SECONDS = 60 * EMAIL_EXPIRES_IN_MINS; +const COOKIE_CACHE_MAX_AGE = 60 * 5; + +// Helper to create flow context for better-auth callbacks +function createAuthFlowContext(contextLabel: string): FlowExecCtx { + return { + flowId: `auth:${contextLabel}:${nanoid(10)}`, + }; +} + +// Singleton controller instances +let authControllerInstance: AuthController | null = null; +let userControllerInstance: UserController | null = null; + +function getAuthControllerInstance(): AuthController { + if (!authControllerInstance) { + authControllerInstance = getAuthController(); + } + return authControllerInstance; +} + +function getUserControllerInstance(): UserController { + if (!userControllerInstance) { + userControllerInstance = getUserController(); + } + return userControllerInstance; +} + +export const auth = betterAuth({ + trustedOrigins: ["http://localhost:5173", settings.betterAuthUrl], + advanced: { useSecureCookies: settings.nodeEnv === "production" }, + appName: settings.appName, + emailAndPassword: { + enabled: true, + disableSignUp: true, + requireEmailVerification: false, + }, + plugins: [ + customSession(async ({ user, session }) => { + session.id = session.token; + return { user, session }; + }), + username({ + minUsernameLength: 5, + maxUsernameLength: 20, + usernameValidator: async (username) => { + const fctx = createAuthFlowContext("username-check"); + const uc = getUserControllerInstance(); + + const result = await uc + .isUsernameAvailable(fctx, username) + .match( + (isAvailable) => ({ success: true, isAvailable }), + (error) => { + logger.error( + `[${fctx.flowId}] Failed to check username availability`, + error, + ); + return { success: false, isAvailable: false }; + }, + ); + + return result.isAvailable; + }, + }), + magicLink({ + expiresIn: EMAIL_EXPIRES_IN_SECONDS, + rateLimit: { window: 60, max: 4 }, + sendMagicLink: async ({ email, token, url }, request) => { + const fctx = createAuthFlowContext("magic-link"); + const ac = getAuthControllerInstance(); + + const result = await ac + .sendMagicLink(fctx, email, token, url) + .match( + () => ({ success: true, error: undefined }), + (error) => ({ success: false, error }), + ); + + if (!result.success || result?.error) { + logger.error( + `[${fctx.flowId}] Failed to send magic link`, + result.error, + ); + throw new APIError("INTERNAL_SERVER_ERROR", { + message: result.error?.message, + }); + } + }, + }), + admin({ + defaultRole: UserRoleMap.admin, + defaultBanReason: + "Stop fanum taxing the server bub, losing aura points fr", + defaultBanExpiresIn: 60 * 60 * 24, + }), + multiSession({ maximumSessions: 5 }), + ], + logger: { + log: (level, message, metadata) => { + logger.log(level, message, metadata); + }, + level: settings.isDevelopment ? "debug" : "info", + }, + database: drizzleAdapter(db, { provider: "pg", schema: { ...schema } }), + secondaryStorage: { + get: async (key) => { + const redis = getRedisInstance(); + return await redis.get(key); + }, + set: async (key, value, ttl) => { + const redis = getRedisInstance(); + if (ttl) { + await redis.setex(key, ttl, value); + } else { + await redis.set(key, value); + } + }, + delete: async (key) => { + const redis = getRedisInstance(); + const out = await redis.del(key); + if (!out && out !== 0) { + return null; + } + return out.toString() as any; + }, + }, + session: { + modelName: "session", + expiresIn: 60 * 60 * 24 * 7, + updateAge: 60 * 60 * 24, + cookieCache: { + enabled: true, + maxAge: COOKIE_CACHE_MAX_AGE, + }, + }, + user: { + changeEmail: { + enabled: true, + sendChangeEmailVerification: async ( + { user, newEmail, url, token }, + request, + ) => { + const fctx = createAuthFlowContext("email-change"); + const ac = getAuthControllerInstance(); + + const result = await ac + .sendEmailChangeVerificationEmail( + fctx, + newEmail, + token, + url, + ) + .match( + () => ({ success: true, error: undefined }), + (error) => ({ success: false, error }), + ); + + if (!result.success || result?.error) { + logger.error( + `[${fctx.flowId}] Failed to send email change verification`, + result.error, + ); + throw new APIError("INTERNAL_SERVER_ERROR", { + message: result.error?.message, + }); + } + }, + }, + modelName: "user", + additionalFields: { + onboardingDone: { + type: "boolean", + defaultValue: false, + required: false, + }, + last2FAVerifiedAt: { type: "date", required: false }, + parentId: { required: false, type: "string" }, + }, + }, +}); + +// - - - diff --git a/packages/logic/domains/auth/controller.ts b/packages/logic/domains/auth/controller.ts new file mode 100644 index 0000000..e42602a --- /dev/null +++ b/packages/logic/domains/auth/controller.ts @@ -0,0 +1,148 @@ +import { AuthContext, MiddlewareContext, MiddlewareOptions } from "better-auth"; +import { AccountRepository } from "../user/account.repository"; +import { FlowExecCtx } from "@/core/flow.execution.context"; +import { ResultAsync } from "neverthrow"; +import type { Err } from "@pkg/result"; +import { authErrors } from "./errors"; +import { logger } from "@pkg/logger"; +import { nanoid } from "nanoid"; +import { db } from "@pkg/db"; + +export class AuthController { + private readonly mins = 10; + + constructor(private accountRepo: AccountRepository) {} + + sendEmailChangeVerificationEmail( + fctx: FlowExecCtx, + newEmail: string, + token: string, + url: string, + ): ResultAsync { + logger.info("Sending email change verification link", { + ...fctx, + newEmail, + }); + logger.debug("Original URL", { ...fctx, url }); + + const transformedUrl = url + .replace("/api/auth/verify-email", "/account/verify-email") + .replace("/api/", "/"); + + logger.debug("Transformed URL", { ...fctx, transformedUrl }); + + // Simulate email sending with 90/10 success/failure + const success = Math.random() > 0.1; + + if (!success) { + logger.error("Failed to send email change verification link", { + ...fctx, + error: "Simulated email service failure", + }); + return ResultAsync.fromPromise( + Promise.reject( + authErrors.emailChangeVerificationFailed( + fctx, + "Simulated email service failure", + ), + ), + (error) => error as Err, + ); + } + + logger.info("Email change verification sent successfully", { + ...fctx, + newEmail, + }); + return ResultAsync.fromSafePromise(Promise.resolve(undefined)); + } + + swapAccountPasswordForTwoFactor( + fctx: FlowExecCtx, + ctx: MiddlewareContext< + MiddlewareOptions, + AuthContext & { returned?: unknown; responseHeaders?: Headers } + >, + ) { + logger.info("Swapping account password for 2FA", { + ...fctx, + }); + + if (!ctx.path.includes("two-factor")) { + return ResultAsync.fromSafePromise(Promise.resolve(ctx)); + } + + if (!ctx.body.password || ctx.body.password.length === 0) { + return ResultAsync.fromSafePromise(Promise.resolve(ctx)); + } + + logger.info("Rotating password for 2FA setup for user", { + ...fctx, + userId: ctx.body.userId, + }); + + return this.accountRepo + .rotatePassword(fctx, ctx.body.userId, nanoid()) + .mapErr((err) => { + logger.error("Failed to rotate password for 2FA", { + ...fctx, + error: err, + }); + return authErrors.passwordRotationFailed(fctx, err.detail); + }) + .map((newPassword) => { + logger.info("Password rotated successfully for 2FA setup", { + ...fctx, + }); + return { + ...ctx, + body: { ...ctx.body, password: newPassword }, + }; + }); + } + + sendMagicLink( + fctx: FlowExecCtx, + email: string, + token: string, + url: string, + ): ResultAsync { + logger.info("Sending magic link", { ...fctx, email }); + logger.debug("Original URL", { ...fctx, url }); + + const transformedUrl = url + .replace("/api/auth/magic-link/verify", "/auth/magic-link") + .replace("/api/", "/"); + + logger.debug("Transformed URL", { ...fctx, transformedUrl }); + + // Simulate email sending with 90/10 success/failure + const success = Math.random() > 0.1; + + if (!success) { + logger.error("Failed to send magic link email", { + ...fctx, + error: "Simulated email service failure", + }); + return ResultAsync.fromPromise( + Promise.reject( + authErrors.magicLinkEmailFailed( + fctx, + "Simulated email service failure", + ), + ), + (error) => error as Err, + ); + } + + logger.info("Magic link email sent successfully", { + ...fctx, + email, + }); + return ResultAsync.fromSafePromise(Promise.resolve(undefined)); + } +} + +export function getAuthController(): AuthController { + return new AuthController(new AccountRepository(db)); +} diff --git a/packages/logic/domains/auth/errors.ts b/packages/logic/domains/auth/errors.ts new file mode 100644 index 0000000..33c3bb1 --- /dev/null +++ b/packages/logic/domains/auth/errors.ts @@ -0,0 +1,59 @@ +import { FlowExecCtx } from "@/core/flow.execution.context"; +import { getError } from "@pkg/logger"; +import { ERROR_CODES, type Err } from "@pkg/result"; + +export const authErrors = { + emailSendFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.INTERNAL_SERVER_ERROR, + message: "Failed to send email", + description: "An error occurred while sending the email", + detail, + }), + + magicLinkEmailFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.INTERNAL_SERVER_ERROR, + message: "Failed to send magic link email", + description: "An error occurred while sending the magic link", + detail, + }), + + emailChangeVerificationFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.INTERNAL_SERVER_ERROR, + message: "Failed to send email change verification link", + description: "An error occurred while sending the verification email", + detail, + }), + + passwordRotationFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.INTERNAL_SERVER_ERROR, + message: "Failed to begin 2FA setup", + description: "An error occurred while rotating the password for 2FA", + detail, + }), + + dbError: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Database operation failed", + description: "Please try again later", + detail, + }), + + accountNotFound: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.NOT_FOUND, + message: "Account not found", + description: "Please try again later", + detail: "Account not found for user", + }), +}; diff --git a/packages/logic/domains/notifications/controller.ts b/packages/logic/domains/notifications/controller.ts new file mode 100644 index 0000000..82c5bb9 --- /dev/null +++ b/packages/logic/domains/notifications/controller.ts @@ -0,0 +1,96 @@ +import { FlowExecCtx } from "@/core/flow.execution.context"; +import { okAsync } from "neverthrow"; +import { + NotificationFilters, + PaginationOptions, +} from "./data"; +import { NotificationRepository } from "./repository"; +import { db } from "@pkg/db"; + +export class NotificationController { + constructor(private notifsRepo: NotificationRepository) {} + + getNotifications( + fctx: FlowExecCtx, + filters: NotificationFilters, + pagination: PaginationOptions, + ) { + return this.notifsRepo.getNotifications(fctx, filters, pagination); + } + + markAsRead( + fctx: FlowExecCtx, + notificationIds: number[], + userId: string, + ) { + return this.notifsRepo.markAsRead(fctx, notificationIds, userId); + } + + markAsUnread( + fctx: FlowExecCtx, + notificationIds: number[], + userId: string, + ) { + return this.notifsRepo.markAsUnread(fctx, notificationIds, userId); + } + + archive( + fctx: FlowExecCtx, + notificationIds: number[], + userId: string, + ) { + return this.notifsRepo.archive(fctx, notificationIds, userId); + } + + unarchive( + fctx: FlowExecCtx, + notificationIds: number[], + userId: string, + ) { + return this.notifsRepo.unarchive(fctx, notificationIds, userId); + } + + deleteNotifications( + fctx: FlowExecCtx, + notificationIds: number[], + userId: string, + ) { + return this.notifsRepo.deleteNotifications(fctx, notificationIds, userId); + } + + getUnreadCount( + fctx: FlowExecCtx, + userId: string, + ) { + return this.notifsRepo.getUnreadCount(fctx, userId); + } + + markAllAsRead( + fctx: FlowExecCtx, + userId: string, + ) { + // Get all unread notification IDs for this user + const filters: NotificationFilters = { + userId, + isRead: false, + isArchived: false, + }; + + // Get a large number to handle bulk operations + const pagination: PaginationOptions = { page: 1, pageSize: 1000 }; + + return this.notifsRepo + .getNotifications(fctx, filters, pagination) + .map((paginated) => paginated.data.map((n) => n.id)) + .andThen((notificationIds) => { + if (notificationIds.length === 0) { + return okAsync(true); + } + return this.notifsRepo.markAsRead(fctx, notificationIds, userId); + }); + } +} + +export function getNotificationController(): NotificationController { + return new NotificationController(new NotificationRepository(db)); +} diff --git a/packages/logic/domains/notifications/data.ts b/packages/logic/domains/notifications/data.ts new file mode 100644 index 0000000..b95eefd --- /dev/null +++ b/packages/logic/domains/notifications/data.ts @@ -0,0 +1,102 @@ +import * as v from "valibot"; + +// Notification schema +export const notificationSchema = v.object({ + id: v.pipe(v.number(), v.integer()), + title: v.string(), + body: v.string(), + priority: v.string(), + type: v.string(), + category: v.string(), + isRead: v.boolean(), + isArchived: v.boolean(), + actionUrl: v.string(), + actionType: v.string(), + actionData: v.string(), + icon: v.string(), + userId: v.string(), + sentAt: v.date(), + readAt: v.nullable(v.date()), + expiresAt: v.nullable(v.date()), + createdAt: v.date(), + updatedAt: v.date(), +}); + +export type Notification = v.InferOutput; +export type Notifications = Notification[]; + +// Notification filters schema +export const notificationFiltersSchema = v.object({ + userId: v.string(), + isRead: v.optional(v.boolean()), + isArchived: v.optional(v.boolean()), + type: v.optional(v.string()), + category: v.optional(v.string()), + priority: v.optional(v.string()), + search: v.optional(v.string()), +}); +export type NotificationFilters = v.InferOutput< + typeof notificationFiltersSchema +>; + +// Pagination options schema +export const paginationOptionsSchema = v.object({ + page: v.pipe(v.number(), v.integer()), + pageSize: v.pipe(v.number(), v.integer()), + sortBy: v.optional(v.string()), + sortOrder: v.optional(v.string()), +}); +export type PaginationOptions = v.InferOutput; + +// Paginated notifications schema +export const paginatedNotificationsSchema = v.object({ + data: v.array(notificationSchema), + total: v.pipe(v.number(), v.integer()), + page: v.pipe(v.number(), v.integer()), + pageSize: v.pipe(v.number(), v.integer()), + totalPages: v.pipe(v.number(), v.integer()), +}); +export type PaginatedNotifications = v.InferOutput< + typeof paginatedNotificationsSchema +>; + +// Get notifications schema +export const getNotificationsSchema = v.object({ + filters: notificationFiltersSchema, + pagination: paginationOptionsSchema, +}); +export type GetNotifications = v.InferOutput; + +// Bulk notification IDs schema +export const bulkNotificationIdsSchema = v.object({ + notificationIds: v.array(v.pipe(v.number(), v.integer())), +}); +export type BulkNotificationIds = v.InferOutput< + typeof bulkNotificationIdsSchema +>; + +// View Model specific types +export const clientNotificationFiltersSchema = v.object({ + userId: v.string(), + isRead: v.optional(v.boolean()), + isArchived: v.optional(v.boolean()), + type: v.optional(v.string()), + category: v.optional(v.string()), + priority: v.optional(v.string()), + search: v.optional(v.string()), +}); +export type ClientNotificationFilters = v.InferOutput< + typeof clientNotificationFiltersSchema +>; + +export const clientPaginationStateSchema = v.object({ + page: v.pipe(v.number(), v.integer()), + pageSize: v.pipe(v.number(), v.integer()), + total: v.pipe(v.number(), v.integer()), + totalPages: v.pipe(v.number(), v.integer()), + sortBy: v.picklist(["createdAt", "sentAt", "readAt", "priority"]), + sortOrder: v.picklist(["asc", "desc"]), +}); +export type ClientPaginationState = v.InferOutput< + typeof clientPaginationStateSchema +>; diff --git a/packages/logic/domains/notifications/errors.ts b/packages/logic/domains/notifications/errors.ts new file mode 100644 index 0000000..d924146 --- /dev/null +++ b/packages/logic/domains/notifications/errors.ts @@ -0,0 +1,78 @@ +import { FlowExecCtx } from "@/core/flow.execution.context"; +import { ERROR_CODES, type Err } from "@pkg/result"; +import { getError } from "@pkg/logger"; + +export const notificationErrors = { + dbError: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Database operation failed", + description: "Please try again later", + detail, + }), + + getNotificationsFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to fetch notifications", + description: "Please try again later", + detail, + }), + + markAsReadFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to mark notifications as read", + description: "Please try again later", + detail, + }), + + markAsUnreadFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to mark notifications as unread", + description: "Please try again later", + detail, + }), + + archiveFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to archive notifications", + description: "Please try again later", + detail, + }), + + unarchiveFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to unarchive notifications", + description: "Please try again later", + detail, + }), + + deleteNotificationsFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to delete notifications", + description: "Please try again later", + detail, + }), + + getUnreadCountFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to get unread count", + description: "Please try again later", + detail, + }), +}; + diff --git a/packages/logic/domains/notifications/repository.ts b/packages/logic/domains/notifications/repository.ts new file mode 100644 index 0000000..e121267 --- /dev/null +++ b/packages/logic/domains/notifications/repository.ts @@ -0,0 +1,384 @@ +import { and, asc, count, Database, desc, eq, like, or, sql } from "@pkg/db"; +import { notifications } from "@pkg/db/schema"; +import { ResultAsync } from "neverthrow"; +import { FlowExecCtx } from "@core/flow.execution.context"; +import type { + Notification, + NotificationFilters, + PaginatedNotifications, + PaginationOptions, +} from "./data"; +import { type Err } from "@pkg/result"; +import { notificationErrors } from "./errors"; +import { logger } from "@pkg/logger"; + +export class NotificationRepository { + constructor(private db: Database) {} + + getNotifications( + fctx: FlowExecCtx, + filters: NotificationFilters, + pagination: PaginationOptions, + ): ResultAsync { + logger.info("Getting notifications with filters", { ...fctx, filters }); + + const { userId, isRead, isArchived, type, category, priority, search } = + filters; + const { + page, + pageSize, + sortBy = "createdAt", + sortOrder = "desc", + } = pagination; + + // Build WHERE conditions + const conditions = [eq(notifications.userId, userId)]; + + if (isRead !== undefined) { + conditions.push(eq(notifications.isRead, isRead)); + } + + if (isArchived !== undefined) { + conditions.push(eq(notifications.isArchived, isArchived)); + } + + if (type) { + conditions.push(eq(notifications.type, type)); + } + + if (category) { + conditions.push(eq(notifications.category, category)); + } + + if (priority) { + conditions.push(eq(notifications.priority, priority)); + } + + if (search) { + conditions.push( + or( + like(notifications.title, `%${search}%`), + like(notifications.body, `%${search}%`), + )!, + ); + } + + const whereClause = and(...conditions); + + return ResultAsync.fromPromise( + this.db.select({ count: count() }).from(notifications).where(whereClause), + (error) => { + logger.error("Failed to get notifications count", { + ...fctx, + error, + }); + return notificationErrors.getNotificationsFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).andThen((totalResult) => { + const total = totalResult[0]?.count || 0; + const offset = (page - 1) * pageSize; + + // Map sortBy to proper column + const getOrderColumn = (sortBy: string) => { + switch (sortBy) { + case "createdAt": + return notifications.createdAt; + case "sentAt": + return notifications.sentAt; + case "readAt": + return notifications.readAt; + case "priority": + return notifications.priority; + default: + return notifications.createdAt; + } + }; + + const orderColumn = getOrderColumn(sortBy); + const orderFunc = sortOrder === "asc" ? asc : desc; + + return ResultAsync.fromPromise( + this.db + .select() + .from(notifications) + .where(whereClause) + .orderBy(orderFunc(orderColumn)) + .limit(pageSize) + .offset(offset), + (error) => { + logger.error("Failed to get notifications data", { + ...fctx, + error, + }); + return notificationErrors.getNotificationsFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map((data) => { + const totalPages = Math.ceil(total / pageSize); + logger.info("Retrieved notifications", { + ...fctx, + count: data.length, + page, + totalPages, + }); + + return { + data: data as Notification[], + total, + page, + pageSize, + totalPages, + }; + }); + }); + } + + markAsRead( + fctx: FlowExecCtx, + notificationIds: number[], + userId: string, + ): ResultAsync { + logger.info("Marking notifications as read", { + ...fctx, + notificationIds, + userId, + }); + + return ResultAsync.fromPromise( + this.db + .update(notifications) + .set({ + isRead: true, + readAt: new Date(), + updatedAt: new Date(), + }) + .where( + and( + eq(notifications.userId, userId), + sql`${notifications.id} = ANY(${notificationIds})`, + ), + ), + (error) => { + logger.error("Failed to mark notifications as read", { + ...fctx, + error, + }); + return notificationErrors.markAsReadFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map(() => { + logger.info("Notifications marked as read successfully", { + ...fctx, + notificationIds, + }); + return true; + }); + } + + markAsUnread( + fctx: FlowExecCtx, + notificationIds: number[], + userId: string, + ): ResultAsync { + logger.info("Marking notifications as unread", { + ...fctx, + notificationIds, + userId, + }); + + return ResultAsync.fromPromise( + this.db + .update(notifications) + .set({ + isRead: false, + readAt: null, + updatedAt: new Date(), + }) + .where( + and( + eq(notifications.userId, userId), + sql`${notifications.id} = ANY(${notificationIds})`, + ), + ), + (error) => { + logger.error("Failed to mark notifications as unread", { + ...fctx, + error, + }); + return notificationErrors.markAsUnreadFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map(() => { + logger.info("Notifications marked as unread successfully", { + ...fctx, + notificationIds, + }); + return true; + }); + } + + archive( + fctx: FlowExecCtx, + notificationIds: number[], + userId: string, + ): ResultAsync { + logger.info("Archiving notifications", { + ...fctx, + notificationIds, + userId, + }); + + return ResultAsync.fromPromise( + this.db + .update(notifications) + .set({ + isArchived: true, + updatedAt: new Date(), + }) + .where( + and( + eq(notifications.userId, userId), + sql`${notifications.id} = ANY(${notificationIds})`, + ), + ), + (error) => { + logger.error("Failed to archive notifications", { + ...fctx, + error, + }); + return notificationErrors.archiveFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map(() => { + logger.info("Notifications archived successfully", { + ...fctx, + notificationIds, + }); + return true; + }); + } + + unarchive( + fctx: FlowExecCtx, + notificationIds: number[], + userId: string, + ): ResultAsync { + logger.info("Unarchiving notifications", { + ...fctx, + notificationIds, + userId, + }); + + return ResultAsync.fromPromise( + this.db + .update(notifications) + .set({ + isArchived: false, + updatedAt: new Date(), + }) + .where( + and( + eq(notifications.userId, userId), + sql`${notifications.id} = ANY(${notificationIds})`, + ), + ), + (error) => { + logger.error("Failed to unarchive notifications", { + ...fctx, + error, + }); + return notificationErrors.unarchiveFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map(() => { + logger.info("Notifications unarchived successfully", { + ...fctx, + notificationIds, + }); + return true; + }); + } + + deleteNotifications( + fctx: FlowExecCtx, + notificationIds: number[], + userId: string, + ): ResultAsync { + logger.info("Deleting notifications", { + ...fctx, + notificationIds, + userId, + }); + + return ResultAsync.fromPromise( + this.db + .delete(notifications) + .where( + and( + eq(notifications.userId, userId), + sql`${notifications.id} = ANY(${notificationIds})`, + ), + ), + (error) => { + logger.error("Failed to delete notifications", { + ...fctx, + error, + }); + return notificationErrors.deleteNotificationsFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map(() => { + logger.info("Notifications deleted successfully", { + ...fctx, + notificationIds, + }); + return true; + }); + } + + getUnreadCount( + fctx: FlowExecCtx, + userId: string, + ): ResultAsync { + logger.info("Getting unread count", { ...fctx, userId }); + + return ResultAsync.fromPromise( + this.db + .select({ count: count() }) + .from(notifications) + .where( + and( + eq(notifications.userId, userId), + eq(notifications.isRead, false), + eq(notifications.isArchived, false), + ), + ), + (error) => { + logger.error("Failed to get unread count", { ...fctx, error }); + return notificationErrors.getUnreadCountFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map((result) => { + const count = result[0]?.count || 0; + logger.info("Retrieved unread count", { ...fctx, count }); + return count; + }); + } +} diff --git a/packages/logic/domains/notifications/router.ts b/packages/logic/domains/notifications/router.ts new file mode 100644 index 0000000..0e2781e --- /dev/null +++ b/packages/logic/domains/notifications/router.ts @@ -0,0 +1,160 @@ +import { bulkNotificationIdsSchema, getNotificationsSchema } from "./data"; +import { getNotificationController } from "./controller"; +import { sValidator } from "@hono/standard-validator"; +import { HonoContext } from "@core/hono.helpers"; +import { Hono } from "hono"; + +const nc = getNotificationController(); + +export const notificationsRouter = new Hono() + .get("/", async (c) => { + const fctx = c.env.locals.fCtx; + const userId = c.env.locals.user.id; + const url = new URL(c.req.url); + + const filters = { + userId, + isRead: url.searchParams.get("isRead") + ? url.searchParams.get("isRead") === "true" + : undefined, + isArchived: url.searchParams.get("isArchived") + ? url.searchParams.get("isArchived") === "true" + : undefined, + type: url.searchParams.get("type") || undefined, + category: url.searchParams.get("category") || undefined, + priority: url.searchParams.get("priority") || undefined, + search: url.searchParams.get("search") || undefined, + }; + + const pagination = { + page: parseInt(url.searchParams.get("page") || "1"), + pageSize: parseInt(url.searchParams.get("pageSize") || "20"), + sortBy: url.searchParams.get("sortBy") || "createdAt", + sortOrder: url.searchParams.get("sortOrder") || "desc", + }; + + const res = await nc.getNotifications(fctx, filters, pagination); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }) + .post( + "/get-notifications", + sValidator("json", getNotificationsSchema), + async (c) => { + const fctx = c.env.locals.fCtx; + const data = c.req.valid("json"); + const res = await nc.getNotifications(fctx, data.filters, data.pagination); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }, + ) + .put( + "/mark-read", + sValidator("json", bulkNotificationIdsSchema), + async (c) => { + const fctx = c.env.locals.fCtx; + const data = c.req.valid("json"); + const userId = c.env.locals.user.id; + const res = await nc.markAsRead(fctx, [...data.notificationIds], userId); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }, + ) + .put( + "/mark-unread", + sValidator("json", bulkNotificationIdsSchema), + async (c) => { + const fctx = c.env.locals.fCtx; + const data = c.req.valid("json"); + const userId = c.env.locals.user.id; + const res = await nc.markAsUnread(fctx, [...data.notificationIds], userId); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }, + ) + .put( + "/archive", + sValidator("json", bulkNotificationIdsSchema), + async (c) => { + const fctx = c.env.locals.fCtx; + const data = c.req.valid("json"); + const userId = c.env.locals.user.id; + const res = await nc.archive(fctx, [...data.notificationIds], userId); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }, + ) + .put( + "/unarchive", + sValidator("json", bulkNotificationIdsSchema), + async (c) => { + const fctx = c.env.locals.fCtx; + const data = c.req.valid("json"); + const userId = c.env.locals.user.id; + const res = await nc.unarchive(fctx, [...data.notificationIds], userId); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }, + ) + .delete( + "/delete", + sValidator("json", bulkNotificationIdsSchema), + async (c) => { + const fctx = c.env.locals.fCtx; + const data = c.req.valid("json"); + const userId = c.env.locals.user.id; + const res = await nc.deleteNotifications(fctx, [...data.notificationIds], userId); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }, + ) + .put("/mark-all-read", async (c) => { + const fctx = c.env.locals.fCtx; + const userId = c.env.locals.user.id; + const res = await nc.markAllAsRead(fctx, userId); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }) + .get("/unread-count", async (c) => { + const fctx = c.env.locals.fCtx; + const userId = c.env.locals.user.id; + const res = await nc.getUnreadCount(fctx, userId); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }); diff --git a/packages/logic/domains/user/account.repository.ts b/packages/logic/domains/user/account.repository.ts new file mode 100644 index 0000000..7a05071 --- /dev/null +++ b/packages/logic/domains/user/account.repository.ts @@ -0,0 +1,213 @@ +import { FlowExecCtx } from "@/core/flow.execution.context"; +import { ERROR_CODES, type Err } from "@pkg/result"; +import { getError, logger } from "@pkg/logger"; +import { auth } from "../auth/config.base"; +import { account } from "@pkg/db/schema"; +import { ResultAsync } from "neverthrow"; +import { Database, eq } from "@pkg/db"; +import { nanoid } from "nanoid"; + +export class AccountRepository { + constructor(private db: Database) {} + + private dbError(fctx: FlowExecCtx, detail: string): Err { + return getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Database operation failed", + description: "Please try again later", + detail, + }); + } + + private accountNotFound(fctx: FlowExecCtx): Err { + return getError({ + flowId: fctx.flowId, + code: ERROR_CODES.NOT_FOUND, + message: "Account not found", + description: "Please try again later", + detail: "Account not found for user", + }); + } + + ensureAccountExists( + fctx: FlowExecCtx, + userId: string, + ): ResultAsync { + logger.info("Checking if account exists for user", { + ...fctx, + userId, + }); + + return ResultAsync.fromPromise( + this.db.query.account.findFirst({ + where: eq(account.userId, userId), + }), + (error) => { + logger.error("Failed to check account existence", { + ...fctx, + error, + }); + return this.dbError( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).andThen((existingAccount) => { + if (existingAccount) { + logger.info("Account already exists for user", { + ...fctx, + userId, + }); + return ResultAsync.fromSafePromise(Promise.resolve(true)); + } + + logger.info( + "Account does not exist, creating new account for user", + { + ...fctx, + userId, + }, + ); + + return ResultAsync.fromPromise( + auth.$context.then((ctx) => ctx.password.hash(nanoid())), + (error) => { + logger.error("Failed to hash password", { + ...fctx, + error, + }); + return this.dbError( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).andThen((password) => { + const aid = nanoid(); + + return ResultAsync.fromPromise( + this.db + .insert(account) + .values({ + id: aid, + accountId: userId, + providerId: "credential", + userId: userId, + password, + createdAt: new Date(), + updatedAt: new Date(), + }) + .execute(), + (error) => { + logger.error("Failed to create account", { + ...fctx, + error, + }); + return this.dbError( + fctx, + error instanceof Error + ? error.message + : String(error), + ); + }, + ).map(() => { + logger.info("Account created successfully for user", { + ...fctx, + userId, + }); + return false; + }); + }); + }); + } + + rotatePassword( + fctx: FlowExecCtx, + userId: string, + password: string, + ): ResultAsync { + logger.info("Starting password rotation for user", { + ...fctx, + userId, + }); + + return ResultAsync.fromPromise( + this.db.query.account.findFirst({ + where: eq(account.userId, userId), + }), + (error) => { + logger.error( + "Failed to check account existence for password rotation", + { + ...fctx, + error, + }, + ); + return this.dbError( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).andThen((existingAccount) => { + if (!existingAccount) { + logger.error("Account not found for user", { + ...fctx, + userId, + }); + return ResultAsync.fromSafePromise( + Promise.resolve(this.accountNotFound(fctx)), + ).andThen((err) => + ResultAsync.fromSafePromise(Promise.reject(err)), + ); + } + + return ResultAsync.fromPromise( + auth.$context.then((ctx) => ctx.password.hash(password)), + (error) => { + logger.error("Failed to hash password for rotation", { + ...fctx, + error, + }); + return this.dbError( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).andThen((hashed) => { + logger.info("Updating user's password in database", { + ...fctx, + }); + + return ResultAsync.fromPromise( + this.db + .update(account) + .set({ password: hashed }) + .where(eq(account.userId, userId)) + .returning() + .execute(), + (error) => { + logger.error("Failed to update password", { + ...fctx, + error, + }); + return this.dbError( + fctx, + error instanceof Error + ? error.message + : String(error), + ); + }, + ).map((result) => { + logger.info("User's password updated successfully", { + ...fctx, + }); + logger.debug("Password rotation result", { + ...fctx, + result, + }); + return password; + }); + }); + }); + } +} diff --git a/packages/logic/domains/user/controller.ts b/packages/logic/domains/user/controller.ts new file mode 100644 index 0000000..b432ff0 --- /dev/null +++ b/packages/logic/domains/user/controller.ts @@ -0,0 +1,55 @@ +import { FlowExecCtx } from "@/core/flow.execution.context"; +import { AccountRepository } from "./account.repository"; +import { UserRepository } from "./repository"; +import { db } from "@pkg/db"; + +export class UserController { + constructor( + private userRepository: UserRepository, + private accountRepo: AccountRepository, + ) {} + + getUserInfo(fctx: FlowExecCtx, userId: string) { + return this.userRepository.getUserInfo(fctx, userId); + } + + ensureAccountExists(fctx: FlowExecCtx, userId: string) { + return this.accountRepo.ensureAccountExists(fctx, userId); + } + + isUsernameAvailable(fctx: FlowExecCtx, username: string) { + return this.userRepository.isUsernameAvailable(fctx, username); + } + + updateLastVerified2FaAtToNow(fctx: FlowExecCtx, userId: string) { + return this.userRepository.updateLastVerified2FaAtToNow(fctx, userId); + } + + banUser( + fctx: FlowExecCtx, + userId: string, + reason: string, + banExpiresAt: Date, + ) { + return this.userRepository.banUser(fctx, userId, reason, banExpiresAt); + } + + isUserBanned(fctx: FlowExecCtx, userId: string) { + return this.userRepository.isUserBanned(fctx, userId); + } + + getBanInfo(fctx: FlowExecCtx, userId: string) { + return this.userRepository.getBanInfo(fctx, userId); + } + + rotatePassword(fctx: FlowExecCtx, userId: string, password: string) { + return this.accountRepo.rotatePassword(fctx, userId, password); + } +} + +export function getUserController(): UserController { + return new UserController( + new UserRepository(db), + new AccountRepository(db), + ); +} diff --git a/packages/logic/domains/user/data.ts b/packages/logic/domains/user/data.ts new file mode 100644 index 0000000..70e660c --- /dev/null +++ b/packages/logic/domains/user/data.ts @@ -0,0 +1,159 @@ +import { Session } from "better-auth"; +import * as v from "valibot"; + +export type { Session } from "better-auth"; + +export type ModifiedSession = Session & { isCurrent?: boolean }; + +// User role enum +export enum UserRoleMap { + user = "user", + admin = "admin", +} + +// User role schema +export const userRoleSchema = v.picklist(["user", "admin"]); +export type UserRole = v.InferOutput; + +// User schema +export const userSchema = v.object({ + id: v.string(), + name: v.string(), + email: v.string(), + emailVerified: v.boolean(), + image: v.optional(v.string()), + createdAt: v.date(), + updatedAt: v.date(), + username: v.optional(v.string()), + displayUsername: v.optional(v.string()), + role: v.optional(v.string()), + banned: v.optional(v.boolean()), + banReason: v.optional(v.string()), + banExpires: v.optional(v.date()), + onboardingDone: v.optional(v.boolean()), + last2FAVerifiedAt: v.optional(v.date()), + parentId: v.optional(v.string()), +}); +export type User = v.InferOutput; + +// Account schema +export const accountSchema = v.object({ + id: v.string(), + accountId: v.string(), + providerId: v.string(), + userId: v.string(), + accessToken: v.string(), + refreshToken: v.string(), + idToken: v.string(), + accessTokenExpiresAt: v.date(), + refreshTokenExpiresAt: v.date(), + scope: v.string(), + password: v.string(), + createdAt: v.date(), + updatedAt: v.date(), +}); +export type Account = v.InferOutput; + +// Ensure account exists schema +export const ensureAccountExistsSchema = v.object({ + userId: v.string(), +}); +export type EnsureAccountExists = v.InferOutput< + typeof ensureAccountExistsSchema +>; + +// Ban info schema +export const banInfoSchema = v.object({ + banned: v.boolean(), + reason: v.optional(v.string()), + expires: v.optional(v.date()), +}); +export type BanInfo = v.InferOutput; + +// Ban user schema +export const banUserSchema = v.object({ + userId: v.string(), + reason: v.string(), + banExpiresAt: v.date(), +}); +export type BanUser = v.InferOutput; + +// Check username availability schema +export const checkUsernameSchema = v.object({ + username: v.string(), +}); +export type CheckUsername = v.InferOutput; + +// Rotate password schema +export const rotatePasswordSchema = v.object({ + userId: v.string(), + password: v.string(), +}); +export type RotatePassword = v.InferOutput; + +// View Model specific types + +// Search and filter types +export const searchFieldSchema = v.picklist(["email", "name", "username"]); +export type SearchField = v.InferOutput; + +export const searchOperatorSchema = v.picklist([ + "contains", + "starts_with", + "ends_with", +]); +export type SearchOperator = v.InferOutput; + +export const filterOperatorSchema = v.picklist([ + "eq", + "ne", + "lt", + "lte", + "gt", + "gte", +]); +export type FilterOperator = v.InferOutput; + +export const sortDirectionSchema = v.picklist(["asc", "desc"]); +export type SortDirection = v.InferOutput; + +// Users query state +export const usersQueryStateSchema = v.object({ + // searching + searchValue: v.optional(v.string()), + searchField: v.optional(searchFieldSchema), + searchOperator: v.optional(searchOperatorSchema), + + // pagination + limit: v.pipe(v.number(), v.integer()), + offset: v.pipe(v.number(), v.integer()), + + // sorting + sortBy: v.optional(v.string()), + sortDirection: v.optional(sortDirectionSchema), + + // filtering + filterField: v.optional(v.string()), + filterValue: v.optional(v.union([v.string(), v.number(), v.boolean()])), + filterOperator: v.optional(filterOperatorSchema), +}); +export type UsersQueryState = v.InferOutput; + +// UI View Model types + +export const banExpiryModeSchema = v.picklist([ + "never", + "1d", + "7d", + "30d", + "custom", +]); +export type BanExpiryMode = v.InferOutput; + +export const createUserFormSchema = v.object({ + email: v.string(), + password: v.string(), + name: v.string(), + role: v.union([userRoleSchema, v.array(userRoleSchema)]), +}); +export type CreateUserForm = v.InferOutput; diff --git a/packages/logic/domains/user/errors.ts b/packages/logic/domains/user/errors.ts new file mode 100644 index 0000000..ad178b8 --- /dev/null +++ b/packages/logic/domains/user/errors.ts @@ -0,0 +1,77 @@ +import { FlowExecCtx } from "@/core/flow.execution.context"; +import { ERROR_CODES, type Err } from "@pkg/result"; +import { getError } from "@pkg/logger"; + +export const userErrors = { + dbError: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Database operation failed", + description: "Please try again later", + detail, + }), + + userNotFound: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.NOT_FOUND, + message: "User not found", + description: "Try with a different user id", + detail: "User not found in database", + }), + + usernameCheckFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "An error occurred while checking username availability", + description: "Try again later", + detail, + }), + + banOperationFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to perform ban operation", + description: "Please try again later", + detail, + }), + + unbanFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to unban user", + description: "Please try again later", + detail, + }), + + updateFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to update user", + description: "Please try again later", + detail, + }), + + getUserInfoFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "An error occurred while getting user info", + description: "Try again later", + detail, + }), + + getBanInfoFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "An error occurred while getting ban info", + description: "Try again later", + detail, + }), +}; diff --git a/packages/logic/domains/user/repository.ts b/packages/logic/domains/user/repository.ts new file mode 100644 index 0000000..240badb --- /dev/null +++ b/packages/logic/domains/user/repository.ts @@ -0,0 +1,289 @@ +import { ResultAsync, errAsync, okAsync } from "neverthrow"; +import { FlowExecCtx } from "@core/flow.execution.context"; +import { type Err } from "@pkg/result"; +import { Database, eq } from "@pkg/db"; +import { BanInfo, User } from "./data"; +import { user } from "@pkg/db/schema"; +import { userErrors } from "./errors"; +import { logger } from "@pkg/logger"; + +export class UserRepository { + constructor(private db: Database) {} + + getUserInfo(fctx: FlowExecCtx, userId: string): ResultAsync { + logger.info("Getting user info for user", { + flowId: fctx.flowId, + userId, + }); + + return ResultAsync.fromPromise( + this.db.query.user.findFirst({ + where: eq(user.id, userId), + }), + (error) => { + logger.error("Failed to get user info", { + flowId: fctx.flowId, + error, + }); + return userErrors.getUserInfoFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).andThen((userData) => { + if (!userData) { + logger.error("User not found with id", { + flowId: fctx.flowId, + userId, + }); + return errAsync(userErrors.userNotFound(fctx)); + } + + logger.info("User info retrieved successfully for user", { + flowId: fctx.flowId, + userId, + }); + return okAsync(userData as User); + }); + } + + updateLastVerified2FaAtToNow( + fctx: FlowExecCtx, + userId: string, + ): ResultAsync { + logger.info("Updating last 2FA verified timestamp for user", { + flowId: fctx.flowId, + userId, + }); + + return ResultAsync.fromPromise( + this.db + .update(user) + .set({ last2FAVerifiedAt: new Date() }) + .where(eq(user.id, userId)) + .execute(), + (error) => { + logger.error("Failed to update last 2FA verified timestamp", { + ...fctx, + error, + }); + return userErrors.updateFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map(() => { + logger.info("Last 2FA verified timestamp updated successfully", { + ...fctx, + }); + return true; + }); + } + + isUsernameAvailable( + fctx: FlowExecCtx, + username: string, + ): ResultAsync { + logger.info("Checking username availability", { + ...fctx, + username, + }); + + return ResultAsync.fromPromise( + this.db.query.user.findFirst({ + where: eq(user.username, username), + }), + (error) => { + logger.error("Failed to check username availability", { + ...fctx, + error, + }); + return userErrors.usernameCheckFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map((existingUser) => { + const isAvailable = !existingUser?.id; + logger.info("Username availability checked", { + ...fctx, + username, + isAvailable, + }); + return isAvailable; + }); + } + + banUser( + fctx: FlowExecCtx, + userId: string, + reason: string, + banExpiresAt: Date, + ): ResultAsync { + logger.info("Banning user", { + ...fctx, + userId, + banExpiresAt: banExpiresAt.toISOString(), + reason, + }); + + return ResultAsync.fromPromise( + this.db + .update(user) + .set({ + banned: true, + banReason: reason, + banExpires: banExpiresAt, + }) + .where(eq(user.id, userId)) + .execute(), + (error) => { + logger.error("Failed to ban user", { ...fctx, error }); + return userErrors.banOperationFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map(() => { + logger.info("User has been banned", { + ...fctx, + userId, + banExpiresAt: banExpiresAt.toISOString(), + }); + return true; + }); + } + + isUserBanned(fctx: FlowExecCtx, userId: string): ResultAsync { + logger.info("Checking ban status for user", { ...fctx, userId }); + + return ResultAsync.fromPromise( + this.db.query.user.findFirst({ + where: eq(user.id, userId), + columns: { + banned: true, + banExpires: true, + }, + }), + (error) => { + logger.error("Failed to check ban status", { + ...fctx, + error, + }); + return userErrors.dbError( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).andThen((userData) => { + if (!userData) { + logger.error("User not found when checking ban status", { + ...fctx, + }); + return errAsync(userErrors.userNotFound(fctx)); + } + + // If not banned, return false + if (!userData.banned) { + logger.info("User is not banned", { ...fctx, userId }); + return okAsync(false); + } + + // If banned but no expiry date, consider permanently banned + if (!userData.banExpires) { + logger.info("User is permanently banned", { ...fctx, userId }); + return okAsync(true); + } + + const now = new Date(); + if (userData.banExpires <= now) { + logger.info("User ban has expired, removing ban status", { + ...fctx, + userId, + }); + + return ResultAsync.fromPromise( + this.db + .update(user) + .set({ + banned: false, + banReason: null, + banExpires: null, + }) + .where(eq(user.id, userId)) + .execute(), + (error) => { + logger.error("Failed to unban user after expiry", { + ...fctx, + error, + }); + return userErrors.unbanFailed( + fctx, + error instanceof Error + ? error.message + : String(error), + ); + }, + ) + .map(() => { + logger.info("User has been unbanned after expiry", { + ...fctx, + userId, + }); + return false; + }) + .orElse((error) => { + logger.error( + "Failed to unban user after expiry, still returning banned status", + { ...fctx, userId, error }, + ); + // Still return banned status since we couldn't update + return okAsync(true); + }); + } + + logger.info("User is banned", { + ...fctx, + userId, + banExpires: userData.banExpires.toISOString(), + }); + return okAsync(true); + }); + } + + getBanInfo(fctx: FlowExecCtx, userId: string): ResultAsync { + logger.info("Getting ban info for user", { ...fctx, userId }); + + return ResultAsync.fromPromise( + this.db.query.user.findFirst({ + where: eq(user.id, userId), + columns: { banned: true, banReason: true, banExpires: true }, + }), + (error) => { + logger.error("Failed to get ban info", { ...fctx, error }); + return userErrors.getBanInfoFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).andThen((userData) => { + if (!userData) { + logger.error("User not found when getting ban info", { + ...fctx, + }); + return errAsync(userErrors.userNotFound(fctx)); + } + + logger.info("Ban info retrieved successfully for user", { + ...fctx, + userId, + }); + + return okAsync({ + banned: userData.banned || false, + reason: userData.banReason || undefined, + expires: userData.banExpires || undefined, + }); + }); + } +} diff --git a/packages/logic/domains/user/router.ts b/packages/logic/domains/user/router.ts new file mode 100644 index 0000000..6e9e91e --- /dev/null +++ b/packages/logic/domains/user/router.ts @@ -0,0 +1,165 @@ +import { + banUserSchema, + checkUsernameSchema, + ensureAccountExistsSchema, + rotatePasswordSchema, +} from "./data"; +import { HonoContext } from "@core/hono.helpers"; +import { sValidator } from "@hono/standard-validator"; +import { getUserController } from "./controller"; +import { Hono } from "hono"; + +const uc = getUserController(); + +export const usersRouter = new Hono() + // Get current user info + .get("/me", async (c) => { + const fctx = c.env.locals.fCtx; + const userId = c.env.locals.user?.id; + + if (!userId) { + return c.json( + { + error: { + code: "UNAUTHORIZED", + message: "User not authenticated", + description: "Please log in", + detail: "No user ID found in session", + }, + }, + 401, + ); + } + + const res = await uc.getUserInfo(fctx, userId); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }) + + // Get user info by ID + .get("/:userId", async (c) => { + const fctx = c.env.locals.fCtx; + const userId = c.req.param("userId"); + + const res = await uc.getUserInfo(fctx, userId); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }) + + // Ensure account exists + .put( + "/ensure-account-exists", + sValidator("json", ensureAccountExistsSchema), + async (c) => { + const fctx = c.env.locals.fCtx; + const data = c.req.valid("json"); + + const res = await uc.ensureAccountExists(fctx, data.userId); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }, + ) + + // Check username availability + .post( + "/check-username", + sValidator("json", checkUsernameSchema), + async (c) => { + const fctx = c.env.locals.fCtx; + const data = c.req.valid("json"); + + const res = await uc.isUsernameAvailable(fctx, data.username); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }, + ) + + // Update last 2FA verification time + .put("/update-2fa-verified/:userId", async (c) => { + const fctx = c.env.locals.fCtx; + const userId = c.req.param("userId"); + + const res = await uc.updateLastVerified2FaAtToNow(fctx, userId); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }) + + // Ban user + .post("/ban", sValidator("json", banUserSchema), async (c) => { + const fctx = c.env.locals.fCtx; + const data = c.req.valid("json"); + + const res = await uc.banUser(fctx, data.userId, data.reason, data.banExpiresAt); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }) + + // Check if user is banned + .get("/:userId/is-banned", async (c) => { + const fctx = c.env.locals.fCtx; + const userId = c.req.param("userId"); + + const res = await uc.isUserBanned(fctx, userId); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }) + + // Get ban info + .get("/:userId/ban-info", async (c) => { + const fctx = c.env.locals.fCtx; + const userId = c.req.param("userId"); + + const res = await uc.getBanInfo(fctx, userId); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }) + + // Rotate password + .put( + "/rotate-password", + sValidator("json", rotatePasswordSchema), + async (c) => { + const fctx = c.env.locals.fCtx; + const data = c.req.valid("json"); + + const res = await uc.rotatePassword(fctx, data.userId, data.password); + return c.json( + res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }, + res.isOk() ? 200 : 400, + ); + }, + ); diff --git a/packages/logic/package.json b/packages/logic/package.json new file mode 100644 index 0000000..194bacf --- /dev/null +++ b/packages/logic/package.json @@ -0,0 +1,40 @@ +{ + "name": "@pkg/logic", + "scripts": { + "auth:schemagen": "bun x @better-auth/cli generate --config ./domains/auth/config.base.ts --output ../../packages/db/schema/better.auth.schema.ts" + }, + "dependencies": { + "@hono/standard-validator": "^0.2.1", + "@pkg/db": "workspace:*", + "@pkg/logger": "workspace:*", + "@pkg/redis": "workspace:*", + "@pkg/result": "workspace:*", + "@pkg/settings": "workspace:*", + "@types/pdfkit": "^0.14.0", + "argon2": "^0.43.0", + "better-auth": "^1.4.7", + "date-fns-tz": "^3.2.0", + "dotenv": "^16.5.0", + "hono": "^4.11.1", + "imapflow": "^1.0.188", + "mailparser": "^3.7.3", + "nanoid": "^5.1.5", + "neverthrow": "^8.2.0", + "otplib": "^12.0.1", + "pdfkit": "^0.17.1", + "tmp": "^0.2.3", + "uuid": "^11.1.0", + "valibot": "^1.2.0", + "xlsx": "^0.18.5" + }, + "devDependencies": { + "@types/bun": "latest", + "@types/imapflow": "^1.0.22", + "@types/mailparser": "^3.4.6", + "@types/tmp": "^0.2.6", + "@types/uuid": "^10.0.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } +} diff --git a/packages/logic/tsconfig.json b/packages/logic/tsconfig.json new file mode 100644 index 0000000..3c8de5f --- /dev/null +++ b/packages/logic/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "strict": true, + "jsx": "react-jsx", + "jsxImportSource": "hono/jsx", + "baseUrl": ".", + "paths": { + "@/*": ["./*"], + "@domains/*": ["./domains/*"], + "@core/*": ["./core/*"] + }, + "moduleResolution": "bundler", + "module": "esnext", + "target": "esnext" + } +} diff --git a/packages/redis/index.ts b/packages/redis/index.ts new file mode 100644 index 0000000..ce48f0c --- /dev/null +++ b/packages/redis/index.ts @@ -0,0 +1,18 @@ +import { Redis } from "ioredis"; +export * from "ioredis"; + +let redis: Redis | undefined; + +let defaultRedisUrl = process.env.REDIS_URL ?? ""; + +export function getRedisInstance(url: string = defaultRedisUrl) { + if (redis) { + return redis; + } + redis = new Redis(url, { + lazyConnect: true, + connectTimeout: 5000, + commandTimeout: 5000, + }); + return redis; +} diff --git a/packages/redis/package.json b/packages/redis/package.json new file mode 100644 index 0000000..c134105 --- /dev/null +++ b/packages/redis/package.json @@ -0,0 +1,15 @@ +{ + "name": "@pkg/redis", + "module": "index.ts", + "type": "module", + "private": true, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5" + }, + "dependencies": { + "ioredis": "^5.6.1" + } +} diff --git a/packages/result/index.ts b/packages/result/index.ts new file mode 100644 index 0000000..4de398e --- /dev/null +++ b/packages/result/index.ts @@ -0,0 +1,81 @@ +export const ERROR_CODES = { + API_ERROR: "API_ERROR", + EXTERNAL_API_ERROR: "EXTERNAL_API_ERROR", + RATE_LIMIT_ERROR: "RATE_LIMIT_ERROR", + DATABASE_ERROR: "DATABASE_ERROR", + NETWORK_ERROR: "NETWORK_ERROR", + BANNED: "BANNED", + AUTH_ERROR: "AUTH_ERROR", + PERMISSION_ERROR: "PERMISSION_ERROR", + VALIDATION_ERROR: "VALIDATION_ERROR", + UNKNOWN_ERROR: "UNKNOWN_ERROR", + NOT_FOUND_ERROR: "NOT_FOUND_ERROR", + NOT_FOUND: "NOT_FOUND", + INPUT_ERROR: "INPUT_ERROR", + INTERNAL_SERVER_ERROR: "INTERNAL_SERVER_ERROR", + EXTERNAL_SERVICE_ERROR: "EXTERNAL_SERVICE_ERROR", + FILE_SYSTEM_ERROR: "FILE_SYSTEM_ERROR", + STORAGE_ERROR: "STORAGE_ERROR", + NOT_ALLOWED: "NOT_ALLOWED", + NOT_IMPLEMENTED: "NOT_IMPLEMENTED", + PROCESSING_ERROR: "PROCESSING_ERROR", + PARSING_ERROR: "PARSING_ERROR", +} as const; + +export const errorStatusMap = { + [ERROR_CODES.VALIDATION_ERROR]: 400, + [ERROR_CODES.AUTH_ERROR]: 403, + [ERROR_CODES.BANNED]: 403, + [ERROR_CODES.NOT_FOUND]: 404, + [ERROR_CODES.NOT_ALLOWED]: 405, + [ERROR_CODES.RATE_LIMIT_ERROR]: 429, + [ERROR_CODES.DATABASE_ERROR]: 500, + [ERROR_CODES.NETWORK_ERROR]: 500, + [ERROR_CODES.EXTERNAL_API_ERROR]: 500, + [ERROR_CODES.API_ERROR]: 500, + [ERROR_CODES.INTERNAL_SERVER_ERROR]: 500, + [ERROR_CODES.EXTERNAL_SERVICE_ERROR]: 500, + [ERROR_CODES.FILE_SYSTEM_ERROR]: 500, + [ERROR_CODES.STORAGE_ERROR]: 500, + [ERROR_CODES.PROCESSING_ERROR]: 500, + [ERROR_CODES.PARSING_ERROR]: 500, + [ERROR_CODES.NOT_IMPLEMENTED]: 501, +} as Record; + +export type Err = { + flowId?: string; + code: string; + message: string; + description: string; + detail: string; + actionable?: boolean; + error?: any; +}; + +type Success = { data: T; error?: undefined | null }; +type Failure = { data?: undefined | null; error: E }; + +// Legacy now, making use of Effect throughout the project +export type Result = Success | Failure; + +export async function tryCatch( + promise: Promise, + err?: E, +): Promise> { + try { + const data = await promise; + return { data }; + } catch (e) { + return { + // @ts-ignore + error: !!err + ? err + : { + code: "UNKNOWN_ERROR", + message: "An unknown error occurred", + description: "An unknown error occurred", + detail: "An unknown error occurred", + }, + }; + } +} diff --git a/packages/result/package.json b/packages/result/package.json new file mode 100644 index 0000000..051f65c --- /dev/null +++ b/packages/result/package.json @@ -0,0 +1,9 @@ +{ + "name": "@pkg/result", + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } +} diff --git a/packages/settings/index.ts b/packages/settings/index.ts new file mode 100644 index 0000000..d6f361f --- /dev/null +++ b/packages/settings/index.ts @@ -0,0 +1,178 @@ +import * as v from "valibot"; + +import "dotenv/config"; + +/** + * Settings schema using Valibot for validation + */ +export const settingsSchema = v.object({ + appName: v.string(), + nodeEnv: v.string(), + logLevel: v.string(), + + isDevelopment: v.optional(v.boolean()), + + redisUrl: v.string(), + databaseUrl: v.string(), + + internalApiKey: v.string(), + + processorApiUrl: v.string(), + + betterAuthUrl: v.string(), + betterAuthSecret: v.string(), + + twofaSessionExpiryMinutes: v.optional(v.number()), + twofaRequiredHours: v.optional(v.number()), + + defaultAdminEmail: v.string(), + + googleClientId: v.string(), + googleClientSecret: v.string(), + + resendApiKey: v.string(), + fromEmail: v.string(), + + qstashUrl: v.string(), + qstashToken: v.string(), + qstashCurrentSigningKey: v.string(), + qstashNextSigningKey: v.string(), + + axiomDatasetName: v.string(), + axiomApiToken: v.string(), + + // R2/Object Storage settings + r2BucketName: v.string(), + r2Region: v.string(), + r2Endpoint: v.string(), + r2AccessKey: v.string(), + r2SecretKey: v.string(), + r2PublicUrl: v.optional(v.string()), + + // File upload settings + maxFileSize: v.number(), + allowedMimeTypes: v.array(v.string()), + allowedExtensions: v.array(v.string()), +}); + +export type Settings = v.InferOutput; + +/** + * Helper to get environment variable with default value + */ +function getEnv(key: string, defaultValue: string = ""): string { + return process.env[key] ?? defaultValue; +} + +/** + * Helper to get environment variable as number with default value + */ +function getEnvNumber(key: string, defaultValue: number): number { + const value = process.env[key]; + if (!value) return defaultValue; + const parsed = Number(value); + return Number.isNaN(parsed) ? defaultValue : parsed; +} + +/** + * Parse comma-separated string into array + */ +function parseCommaSeparated(value: string): string[] { + return value + .split(",") + .map((item) => item.trim()) + .filter((item) => item.length > 0); +} + +/** + * Load and validate settings from environment variables + */ +function loadSettings(): Settings { + const nodeEnv = getEnv("NODE_ENV", "development"); + + const rawSettings = { + appName: getEnv("APP_NAME", "App"), + nodeEnv, + logLevel: getEnv("LOG_LEVEL", "info"), + + isDevelopment: nodeEnv === "development", + + redisUrl: getEnv("REDIS_URL", "redis://localhost:6379"), + databaseUrl: getEnv("DATABASE_URL"), + + internalApiKey: getEnv("INTERNAL_API_KEY"), + + processorApiUrl: getEnv("PROCESSOR_API_URL", "http://localhost:3000"), + + betterAuthUrl: getEnv("BETTER_AUTH_URL"), + betterAuthSecret: getEnv("BETTER_AUTH_SECRET"), + + twofaSessionExpiryMinutes: getEnvNumber( + "TWOFA_SESSION_EXPIRY_MINUTES", + 10, + ), + twofaRequiredHours: getEnvNumber("TWOFA_REQUIRED_HOURS", 24), + + defaultAdminEmail: getEnv("DEFAULT_ADMIN_EMAIL"), + + googleClientId: getEnv("GOOGLE_CLIENT_ID"), + googleClientSecret: getEnv("GOOGLE_CLIENT_SECRET"), + + resendApiKey: getEnv("RESEND_API_KEY"), + fromEmail: getEnv("FROM_EMAIL"), + + qstashUrl: getEnv("QSTASH_URL"), + qstashToken: getEnv("QSTASH_TOKEN"), + qstashCurrentSigningKey: getEnv("QSTASH_CURRENT_SIGNING_KEY"), + qstashNextSigningKey: getEnv("QSTASH_NEXT_SIGNING_KEY"), + + axiomDatasetName: getEnv("AXIOM_DATASET_NAME"), + axiomApiToken: getEnv("AXIOM_API_TOKEN"), + + // R2/Object Storage settings + r2BucketName: getEnv("R2_BUCKET_NAME"), + r2Region: getEnv("R2_REGION", "auto"), + r2Endpoint: getEnv("R2_ENDPOINT"), + r2AccessKey: getEnv("R2_ACCESS_KEY"), + r2SecretKey: getEnv("R2_SECRET_KEY"), + r2PublicUrl: getEnv("R2_PUBLIC_URL") || undefined, + + // File upload settings + maxFileSize: getEnvNumber("MAX_FILE_SIZE", 10485760), // 10MB default + allowedMimeTypes: parseCommaSeparated( + getEnv( + "ALLOWED_MIME_TYPES", + "image/jpeg,image/png,image/webp,image/gif,application/pdf,text/plain", + ), + ), + allowedExtensions: parseCommaSeparated( + getEnv("ALLOWED_EXTENSIONS", "jpg,jpeg,png,webp,gif,pdf,txt"), + ), + }; + + try { + return v.parse(settingsSchema, rawSettings); + } catch (error) { + console.error("❌ Settings validation failed:"); + if (error instanceof v.ValiError) { + for (const issue of error.issues) { + console.error( + ` - ${issue.path?.map((p: any) => p.key).join(".")}: ${issue.message}`, + ); + } + } else { + console.error(error); + } + throw new Error( + "Failed to load settings. Check environment variables.", + ); + } +} + +export const settings = loadSettings(); + +export const getSetting = (key: K): Settings[K] => { + return settings[key]; +}; + +console.log(`✅ Settings loaded | ${settings.appName} (${settings.nodeEnv})`); diff --git a/packages/settings/package.json b/packages/settings/package.json new file mode 100644 index 0000000..c2f357e --- /dev/null +++ b/packages/settings/package.json @@ -0,0 +1,15 @@ +{ + "name": "@pkg/settings", + "module": "index.ts", + "type": "module", + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "dotenv": "^17.2.3", + "valibot": "^1.2.0" + } +} diff --git a/scripts/migrate.sh b/scripts/migrate.sh new file mode 100755 index 0000000..14ee203 --- /dev/null +++ b/scripts/migrate.sh @@ -0,0 +1,7 @@ +echo "🔄 Migrating Primary DB" + +cd packages/db + +bun run db:migrate + +cd ../../ diff --git a/scripts/populate.env.sh b/scripts/populate.env.sh new file mode 100755 index 0000000..0879bb6 --- /dev/null +++ b/scripts/populate.env.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# copy over the environment variables from the base /.env file to all other apps & packages in apps/* and packages/* + +for dir in apps/*; do + if [ -d "$dir" ]; then + cp .env $dir/.env + fi +done + +for dir in packages/*; do + if [ -d "$dir" ]; then + cp .env $dir/.env + fi +done diff --git a/scripts/prod.start.sh b/scripts/prod.start.sh new file mode 100755 index 0000000..622cd27 --- /dev/null +++ b/scripts/prod.start.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +APP_PATH=$1 + +if [ -z "$APP_PATH" ]; then + echo "Usage: prod.start.sh " + exit 1 +fi + +echo "Starting $APP_PATH" + +cd $APP_PATH + +bun run prod diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..6360d8f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "strictNullChecks": true + } +} \ No newline at end of file diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..321813c --- /dev/null +++ b/turbo.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://turbo.build/schema.json", + "ui": "tui", + "tasks": { + "build": { + "dependsOn": ["^build"], + "inputs": ["$TURBO_DEFAULT$", ".env*"], + "outputs": ["build/**"] + }, + "dev": { + "cache": false, + "persistent": true + }, + "prod": { + "cache": false + }, + "db:migrate": { + "cache": false + }, + "test": { + "cache": false + } + } +}