From 2657f936bc80b711ca41003b8274ab8abebab4f1 Mon Sep 17 00:00:00 2001 From: pluja Date: Mon, 19 May 2025 10:19:49 +0000 Subject: [PATCH] Release 2025-05-19 --- .clinerules | 53 - .cursorrules | 307 - .gitignore | 15 - .npmrc | 1 - .platform/README.md | 64 - .../hooks/postdeploy/01_import_triggers.sh | 4 - .vscode/extensions.json | 13 - .vscode/launch.json | 12 - .vscode/settings.json | 60 - .vscode/tasks.json | 53 - README.md | 62 - docker-compose.dev.yml | 45 - docker-compose.yml | 76 - justfile | 50 - pyworker/.env.example | 21 - pyworker/.gitignore | 174 - pyworker/.python-version | 1 - pyworker/Dockerfile | 10 - pyworker/README.md | 149 - pyworker/docker-compose.yml | 12 - pyworker/pyproject.toml | 24 - pyworker/pyworker/__init__.py | 7 - pyworker/pyworker/__main__.py | 10 - pyworker/pyworker/cli.py | 437 - pyworker/pyworker/config.py | 67 - pyworker/pyworker/database.py | 733 - pyworker/pyworker/scheduler.py | 184 - pyworker/pyworker/tasks/__init__.py | 17 - pyworker/pyworker/tasks/base.py | 64 - pyworker/pyworker/tasks/comment_moderation.py | 112 - pyworker/pyworker/tasks/force_triggers.py | 43 - .../pyworker/tasks/service_score_recalc.py | 325 - pyworker/pyworker/tasks/tos_review.py | 116 - pyworker/pyworker/tasks/user_sentiment.py | 134 - pyworker/pyworker/utils/__init__.py | 1 - pyworker/pyworker/utils/ai.py | 261 - pyworker/pyworker/utils/app_http.py | 31 - pyworker/pyworker/utils/app_logging.py | 36 - pyworker/pyworker/utils/crawl.py | 100 - pyworker/tests/__init__.py | 1 - pyworker/tests/test_tasks.py | 74 - pyworker/uv.lock | 414 - web/.dockerignore | 28 - web/.env.example | 4 - web/.gitignore | 31 - web/.npmrc | 1 - web/.nvmrc | 1 - web/.prettierignore | 5 - web/.prettierrc.mjs | 22 - web/Dockerfile | 27 - web/README.md | 29 - web/astro.config.mjs | 159 - web/docker-entrypoint.sh | 21 - web/eslint.config.js | 147 - web/package-lock.json | 14561 ---------------- web/package.json | 91 - .../20250518085822_initial/migration.sql | 798 - .../migration.sql | 2 - web/prisma/migrations/migration_lock.toml | 3 - web/prisma/schema.prisma | 590 - web/prisma/triggers/01_karma_tx.sql | 265 - web/prisma/triggers/02_service_score.sql | 264 - .../triggers/03_service_user_rating.sql | 57 - .../04_service_verification_status.sql | 48 - web/prisma/triggers/05_service_events.sql | 399 - .../triggers/06_notifications_comments.sql | 227 - .../07_notifications_service_suggestion.sql | 72 - .../08_notifications_service_events.sql | 28 - ...9_notifications_service_status_updates.sql | 37 - .../10_notifications_user_status_change.sql | 62 - web/public/favicon-dev.svg | 5 - web/public/favicon-lightmode.svg | 5 - web/public/favicon-stage.svg | 12 - web/public/favicon.svg | 5 - web/scripts/faker.ts | 1333 -- web/src/actions/account.ts | 221 - web/src/actions/admin/attribute.ts | 134 - web/src/actions/admin/event.ts | 129 - web/src/actions/admin/index.ts | 15 - web/src/actions/admin/service.ts | 234 - web/src/actions/admin/serviceSuggestion.ts | 71 - web/src/actions/admin/user.ts | 288 - web/src/actions/admin/verificationStep.ts | 118 - web/src/actions/comment.ts | 442 - web/src/actions/index.ts | 28 - web/src/actions/notifications.ts | 132 - web/src/actions/service.ts | 104 - web/src/actions/serviceSuggestion.ts | 359 - web/src/assets/fallback-service-image.jpg | Bin 387637 -> 0 bytes web/src/assets/logo-mini-full.svg | 4 - web/src/assets/logo-mini.svg | 4 - web/src/assets/logo-normal.svg | 4 - web/src/assets/logo-small.svg | 4 - web/src/components/AdminOnly.astro | 11 - web/src/components/BadgeSmall.astro | 162 - web/src/components/BadgeStandard.astro | 27 - web/src/components/BaseHead.astro | 133 - web/src/components/Button.astro | 176 - web/src/components/Captcha.astro | 80 - web/src/components/Chat.astro | 86 - web/src/components/ChatMessages.astro | 110 - web/src/components/CommentItem.astro | 504 - web/src/components/CommentModeration.astro | 366 - web/src/components/CommentReply.astro | 172 - web/src/components/CommentSection.astro | 263 - web/src/components/CommentSummary.astro | 128 - web/src/components/CopyButton.astro | 45 - web/src/components/DropdownButton.astro | 58 - .../components/DropdownButtonItemForm.astro | 29 - .../components/DropdownButtonItemLink.astro | 27 - web/src/components/Footer.astro | 51 - web/src/components/FormTimeTrap.astro | 64 - web/src/components/FormatTimeInterval.astro | 82 - web/src/components/Header.astro | 187 - .../HeaderNotificationIndicator.astro | 45 - .../components/HeaderSplashTextScript.astro | 41 - web/src/components/HtmxScript.astro | 15 - web/src/components/InputCardGroup.astro | 119 - web/src/components/InputCheckboxGroup.astro | 49 - web/src/components/InputFile.astro | 38 - web/src/components/InputHoneypotTrap.astro | 20 - web/src/components/InputImageFile.astro | 54 - web/src/components/InputLoginToken.astro | 149 - web/src/components/InputRating.astro | 64 - web/src/components/InputSubmitButton.astro | 26 - web/src/components/InputText.astro | 65 - web/src/components/InputTextArea.astro | 44 - web/src/components/InputWrapper.astro | 74 - web/src/components/KarmaUnlocksTable.astro | 30 - web/src/components/Logo.astro | 65 - web/src/components/OgImage.tsx | 132 - web/src/components/Pagination.astro | 134 - web/src/components/PillsRadioGroup.astro | 41 - web/src/components/ScoreGauge.astro | 175 - web/src/components/ScoreSquare.astro | 106 - web/src/components/ServiceCard.astro | 155 - web/src/components/ServiceFiltersPill.astro | 36 - web/src/components/ServiceLinkButton.astro | 132 - web/src/components/ServicesFilters.astro | 497 - .../components/ServicesSearchResults.astro | 141 - web/src/components/SortArrowIcon.astro | 25 - web/src/components/TailwindJsPluggin.astro | 11 - web/src/components/TimeFormatted.astro | 16 - web/src/components/Tooltip.astro | 93 - .../VerificationWarningBanner.astro | 69 - web/src/constants/accountStatusChange.ts | 68 - web/src/constants/attributeCategories.ts | 60 - web/src/constants/attributeTypes.ts | 110 - web/src/constants/characters.ts | 92 - web/src/constants/commentStatus.ts | 57 - web/src/constants/commentStatusChange.ts | 68 - web/src/constants/commentStatusFilters.ts | 143 - web/src/constants/currencies.ts | 61 - web/src/constants/eventTypes.ts | 108 - web/src/constants/karmaUnlocks.ts | 89 - web/src/constants/kycLevels.ts | 64 - web/src/constants/networks.ts | 38 - web/src/constants/notificationTypes.ts | 70 - web/src/constants/project.ts | 1 - web/src/constants/serviceStatusChange.ts | 48 - web/src/constants/serviceSuggestionStatus.ts | 68 - web/src/constants/serviceSuggestionType.ts | 48 - web/src/constants/serviceUserRoles.ts | 73 - web/src/constants/serviceVisibility.ts | 60 - web/src/constants/splashTexts.ts | 17 - web/src/constants/suggestionStatusChange.ts | 48 - web/src/constants/tosHighlightRating.ts | 63 - web/src/constants/userSentiment.ts | 63 - web/src/constants/verificationStatus.ts | 146 - web/src/env.d.ts | 44 - web/src/icons/anonymous-mask.svg | 4 - web/src/icons/bitcoin.svg | 4 - web/src/icons/coins.svg | 8 - web/src/icons/credit-card.svg | 6 - web/src/icons/diamond-question.svg | 15 - web/src/icons/fingerprint-detailed.svg | 4 - web/src/icons/gun.svg | 6 - web/src/icons/handcuffs.svg | 11 - web/src/icons/i2p.svg | 5 - web/src/icons/monero.svg | 5 - web/src/icons/onion.svg | 5 - web/src/layouts/BaseLayout.astro | 101 - web/src/layouts/MarkdownLayout.astro | 72 - web/src/layouts/MiniLayout.astro | 38 - web/src/lib/accountCreate.ts | 28 - web/src/lib/arrays.ts | 130 - web/src/lib/astro.ts | 11 - web/src/lib/astroActions.ts | 17 - web/src/lib/attributes.ts | 152 - web/src/lib/callActionWithUrlParams.ts | 184 - web/src/lib/captcha.ts | 169 - web/src/lib/captchaValidation.ts | 29 - web/src/lib/cn.ts | 14 - web/src/lib/commentsWithReplies.ts | 174 - web/src/lib/contactMethods.ts | 60 - web/src/lib/defineProtectedAction.ts | 124 - web/src/lib/envVariables.ts | 5 - web/src/lib/errorBanners.ts | 228 - web/src/lib/fileStorage.ts | 78 - web/src/lib/formInputs.ts | 9 - web/src/lib/honeypot.ts | 40 - web/src/lib/impersonation.ts | 39 - web/src/lib/karmaUnlocks.ts | 29 - web/src/lib/makeHelpersForOptions.ts | 144 - web/src/lib/markdown.ts | 5 - web/src/lib/notificationPreferences.ts | 14 - web/src/lib/notifications.ts | 295 - web/src/lib/numbers.ts | 35 - web/src/lib/objects.ts | 164 - web/src/lib/onload.ts | 8 - web/src/lib/parseUrlFilters.ts | 311 - web/src/lib/pluralize.ts | 125 - web/src/lib/prisma.ts | 57 - web/src/lib/redirectUrls.ts | 61 - web/src/lib/redis/redisActionsSessions.ts | 69 - web/src/lib/redis/redisGenericManager.ts | 45 - .../lib/redis/redisImpersonationSessions.ts | 45 - .../redis/redisPreGeneratedSecretTokens.ts | 34 - web/src/lib/redis/redisSessions.ts | 78 - web/src/lib/schema.ts | 10 - web/src/lib/sortSeed.ts | 8 - web/src/lib/strings.ts | 68 - web/src/lib/timeAgo.ts | 49 - web/src/lib/timeTrapSecret.ts | 6 - web/src/lib/urls.ts | 115 - web/src/lib/userCookies.ts | 80 - web/src/lib/userSecretToken.ts | 149 - web/src/lib/zodUtils.ts | 72 - web/src/middleware.ts | 155 - web/src/pages/404.astro | 101 - web/src/pages/500.astro | 105 - web/src/pages/about.md | 211 - web/src/pages/access-denied.astro | 75 - web/src/pages/account/edit.astro | 160 - web/src/pages/account/generate.astro | 117 - web/src/pages/account/impersonate.astro | 32 - web/src/pages/account/index.astro | 908 - web/src/pages/account/login.astro | 82 - web/src/pages/account/logout.astro | 8 - web/src/pages/account/welcome.astro | 114 - web/src/pages/admin/attributes.astro | 751 - web/src/pages/admin/comments.astro | 237 - web/src/pages/admin/index.astro | 76 - .../admin/service-suggestions/[id].astro | 195 - .../admin/service-suggestions/index.astro | 385 - .../pages/admin/services/[slug]/edit.astro | 1367 -- web/src/pages/admin/services/index.astro | 616 - web/src/pages/admin/services/new.astro | 366 - web/src/pages/admin/users/[username].astro | 626 - web/src/pages/admin/users/index.astro | 377 - web/src/pages/attributes.astro | 403 - web/src/pages/events.astro | 474 - web/src/pages/files/[...path].ts | 44 - web/src/pages/index.astro | 700 - web/src/pages/karma.mdx | 72 - web/src/pages/notifications.astro | 332 - web/src/pages/ogimage.png.ts | 24 - web/src/pages/service-suggestion/[id].astro | 163 - web/src/pages/service-suggestion/edit.astro | 102 - web/src/pages/service-suggestion/index.astro | 174 - web/src/pages/service-suggestion/new.astro | 308 - web/src/pages/service/[slug].astro | 1506 -- web/src/pages/u/[username].astro | 912 - web/src/robots.txt.ts | 14 - web/src/styles/global.css | 133 - web/src/types/eslint-plugin-import.d.ts | 14 - web/tsconfig.json | 10 - 267 files changed, 49432 deletions(-) delete mode 100644 .clinerules delete mode 100644 .cursorrules delete mode 100644 .gitignore delete mode 100644 .npmrc delete mode 100644 .platform/README.md delete mode 100644 .platform/hooks/postdeploy/01_import_triggers.sh delete mode 100644 .vscode/extensions.json delete mode 100644 .vscode/launch.json delete mode 100644 .vscode/settings.json delete mode 100644 .vscode/tasks.json delete mode 100644 README.md delete mode 100644 docker-compose.dev.yml delete mode 100644 docker-compose.yml delete mode 100644 justfile delete mode 100644 pyworker/.env.example delete mode 100644 pyworker/.gitignore delete mode 100644 pyworker/.python-version delete mode 100644 pyworker/Dockerfile delete mode 100644 pyworker/README.md delete mode 100644 pyworker/docker-compose.yml delete mode 100644 pyworker/pyproject.toml delete mode 100644 pyworker/pyworker/__init__.py delete mode 100644 pyworker/pyworker/__main__.py delete mode 100644 pyworker/pyworker/cli.py delete mode 100644 pyworker/pyworker/config.py delete mode 100644 pyworker/pyworker/database.py delete mode 100644 pyworker/pyworker/scheduler.py delete mode 100644 pyworker/pyworker/tasks/__init__.py delete mode 100644 pyworker/pyworker/tasks/base.py delete mode 100644 pyworker/pyworker/tasks/comment_moderation.py delete mode 100644 pyworker/pyworker/tasks/force_triggers.py delete mode 100644 pyworker/pyworker/tasks/service_score_recalc.py delete mode 100644 pyworker/pyworker/tasks/tos_review.py delete mode 100644 pyworker/pyworker/tasks/user_sentiment.py delete mode 100644 pyworker/pyworker/utils/__init__.py delete mode 100644 pyworker/pyworker/utils/ai.py delete mode 100644 pyworker/pyworker/utils/app_http.py delete mode 100644 pyworker/pyworker/utils/app_logging.py delete mode 100644 pyworker/pyworker/utils/crawl.py delete mode 100644 pyworker/tests/__init__.py delete mode 100644 pyworker/tests/test_tasks.py delete mode 100644 pyworker/uv.lock delete mode 100644 web/.dockerignore delete mode 100644 web/.env.example delete mode 100644 web/.gitignore delete mode 100644 web/.npmrc delete mode 100644 web/.nvmrc delete mode 100644 web/.prettierignore delete mode 100644 web/.prettierrc.mjs delete mode 100644 web/Dockerfile delete mode 100644 web/README.md delete mode 100644 web/astro.config.mjs delete mode 100644 web/docker-entrypoint.sh delete mode 100644 web/eslint.config.js delete mode 100644 web/package-lock.json delete mode 100644 web/package.json delete mode 100644 web/prisma/migrations/20250518085822_initial/migration.sql delete mode 100644 web/prisma/migrations/20250519091940_last_login_at/migration.sql delete mode 100644 web/prisma/migrations/migration_lock.toml delete mode 100644 web/prisma/schema.prisma delete mode 100644 web/prisma/triggers/01_karma_tx.sql delete mode 100644 web/prisma/triggers/02_service_score.sql delete mode 100644 web/prisma/triggers/03_service_user_rating.sql delete mode 100644 web/prisma/triggers/04_service_verification_status.sql delete mode 100644 web/prisma/triggers/05_service_events.sql delete mode 100644 web/prisma/triggers/06_notifications_comments.sql delete mode 100644 web/prisma/triggers/07_notifications_service_suggestion.sql delete mode 100644 web/prisma/triggers/08_notifications_service_events.sql delete mode 100644 web/prisma/triggers/09_notifications_service_status_updates.sql delete mode 100644 web/prisma/triggers/10_notifications_user_status_change.sql delete mode 100644 web/public/favicon-dev.svg delete mode 100644 web/public/favicon-lightmode.svg delete mode 100644 web/public/favicon-stage.svg delete mode 100644 web/public/favicon.svg delete mode 100755 web/scripts/faker.ts delete mode 100644 web/src/actions/account.ts delete mode 100644 web/src/actions/admin/attribute.ts delete mode 100644 web/src/actions/admin/event.ts delete mode 100644 web/src/actions/admin/index.ts delete mode 100644 web/src/actions/admin/service.ts delete mode 100644 web/src/actions/admin/serviceSuggestion.ts delete mode 100644 web/src/actions/admin/user.ts delete mode 100644 web/src/actions/admin/verificationStep.ts delete mode 100644 web/src/actions/comment.ts delete mode 100644 web/src/actions/index.ts delete mode 100644 web/src/actions/notifications.ts delete mode 100644 web/src/actions/service.ts delete mode 100644 web/src/actions/serviceSuggestion.ts delete mode 100644 web/src/assets/fallback-service-image.jpg delete mode 100644 web/src/assets/logo-mini-full.svg delete mode 100644 web/src/assets/logo-mini.svg delete mode 100644 web/src/assets/logo-normal.svg delete mode 100644 web/src/assets/logo-small.svg delete mode 100644 web/src/components/AdminOnly.astro delete mode 100644 web/src/components/BadgeSmall.astro delete mode 100644 web/src/components/BadgeStandard.astro delete mode 100644 web/src/components/BaseHead.astro delete mode 100644 web/src/components/Button.astro delete mode 100644 web/src/components/Captcha.astro delete mode 100644 web/src/components/Chat.astro delete mode 100644 web/src/components/ChatMessages.astro delete mode 100644 web/src/components/CommentItem.astro delete mode 100644 web/src/components/CommentModeration.astro delete mode 100644 web/src/components/CommentReply.astro delete mode 100644 web/src/components/CommentSection.astro delete mode 100644 web/src/components/CommentSummary.astro delete mode 100644 web/src/components/CopyButton.astro delete mode 100644 web/src/components/DropdownButton.astro delete mode 100644 web/src/components/DropdownButtonItemForm.astro delete mode 100644 web/src/components/DropdownButtonItemLink.astro delete mode 100644 web/src/components/Footer.astro delete mode 100644 web/src/components/FormTimeTrap.astro delete mode 100644 web/src/components/FormatTimeInterval.astro delete mode 100644 web/src/components/Header.astro delete mode 100644 web/src/components/HeaderNotificationIndicator.astro delete mode 100644 web/src/components/HeaderSplashTextScript.astro delete mode 100644 web/src/components/HtmxScript.astro delete mode 100644 web/src/components/InputCardGroup.astro delete mode 100644 web/src/components/InputCheckboxGroup.astro delete mode 100644 web/src/components/InputFile.astro delete mode 100644 web/src/components/InputHoneypotTrap.astro delete mode 100644 web/src/components/InputImageFile.astro delete mode 100644 web/src/components/InputLoginToken.astro delete mode 100644 web/src/components/InputRating.astro delete mode 100644 web/src/components/InputSubmitButton.astro delete mode 100644 web/src/components/InputText.astro delete mode 100644 web/src/components/InputTextArea.astro delete mode 100644 web/src/components/InputWrapper.astro delete mode 100644 web/src/components/KarmaUnlocksTable.astro delete mode 100644 web/src/components/Logo.astro delete mode 100644 web/src/components/OgImage.tsx delete mode 100644 web/src/components/Pagination.astro delete mode 100644 web/src/components/PillsRadioGroup.astro delete mode 100644 web/src/components/ScoreGauge.astro delete mode 100644 web/src/components/ScoreSquare.astro delete mode 100644 web/src/components/ServiceCard.astro delete mode 100644 web/src/components/ServiceFiltersPill.astro delete mode 100644 web/src/components/ServiceLinkButton.astro delete mode 100644 web/src/components/ServicesFilters.astro delete mode 100644 web/src/components/ServicesSearchResults.astro delete mode 100644 web/src/components/SortArrowIcon.astro delete mode 100644 web/src/components/TailwindJsPluggin.astro delete mode 100644 web/src/components/TimeFormatted.astro delete mode 100644 web/src/components/Tooltip.astro delete mode 100644 web/src/components/VerificationWarningBanner.astro delete mode 100644 web/src/constants/accountStatusChange.ts delete mode 100644 web/src/constants/attributeCategories.ts delete mode 100644 web/src/constants/attributeTypes.ts delete mode 100644 web/src/constants/characters.ts delete mode 100644 web/src/constants/commentStatus.ts delete mode 100644 web/src/constants/commentStatusChange.ts delete mode 100644 web/src/constants/commentStatusFilters.ts delete mode 100644 web/src/constants/currencies.ts delete mode 100644 web/src/constants/eventTypes.ts delete mode 100644 web/src/constants/karmaUnlocks.ts delete mode 100644 web/src/constants/kycLevels.ts delete mode 100644 web/src/constants/networks.ts delete mode 100644 web/src/constants/notificationTypes.ts delete mode 100644 web/src/constants/project.ts delete mode 100644 web/src/constants/serviceStatusChange.ts delete mode 100644 web/src/constants/serviceSuggestionStatus.ts delete mode 100644 web/src/constants/serviceSuggestionType.ts delete mode 100644 web/src/constants/serviceUserRoles.ts delete mode 100644 web/src/constants/serviceVisibility.ts delete mode 100644 web/src/constants/splashTexts.ts delete mode 100644 web/src/constants/suggestionStatusChange.ts delete mode 100644 web/src/constants/tosHighlightRating.ts delete mode 100644 web/src/constants/userSentiment.ts delete mode 100644 web/src/constants/verificationStatus.ts delete mode 100644 web/src/env.d.ts delete mode 100644 web/src/icons/anonymous-mask.svg delete mode 100644 web/src/icons/bitcoin.svg delete mode 100644 web/src/icons/coins.svg delete mode 100644 web/src/icons/credit-card.svg delete mode 100644 web/src/icons/diamond-question.svg delete mode 100644 web/src/icons/fingerprint-detailed.svg delete mode 100644 web/src/icons/gun.svg delete mode 100644 web/src/icons/handcuffs.svg delete mode 100644 web/src/icons/i2p.svg delete mode 100644 web/src/icons/monero.svg delete mode 100644 web/src/icons/onion.svg delete mode 100644 web/src/layouts/BaseLayout.astro delete mode 100644 web/src/layouts/MarkdownLayout.astro delete mode 100644 web/src/layouts/MiniLayout.astro delete mode 100644 web/src/lib/accountCreate.ts delete mode 100644 web/src/lib/arrays.ts delete mode 100644 web/src/lib/astro.ts delete mode 100644 web/src/lib/astroActions.ts delete mode 100644 web/src/lib/attributes.ts delete mode 100644 web/src/lib/callActionWithUrlParams.ts delete mode 100644 web/src/lib/captcha.ts delete mode 100644 web/src/lib/captchaValidation.ts delete mode 100644 web/src/lib/cn.ts delete mode 100644 web/src/lib/commentsWithReplies.ts delete mode 100644 web/src/lib/contactMethods.ts delete mode 100644 web/src/lib/defineProtectedAction.ts delete mode 100644 web/src/lib/envVariables.ts delete mode 100644 web/src/lib/errorBanners.ts delete mode 100644 web/src/lib/fileStorage.ts delete mode 100644 web/src/lib/formInputs.ts delete mode 100644 web/src/lib/honeypot.ts delete mode 100644 web/src/lib/impersonation.ts delete mode 100644 web/src/lib/karmaUnlocks.ts delete mode 100644 web/src/lib/makeHelpersForOptions.ts delete mode 100644 web/src/lib/markdown.ts delete mode 100644 web/src/lib/notificationPreferences.ts delete mode 100644 web/src/lib/notifications.ts delete mode 100644 web/src/lib/numbers.ts delete mode 100644 web/src/lib/objects.ts delete mode 100644 web/src/lib/onload.ts delete mode 100644 web/src/lib/parseUrlFilters.ts delete mode 100644 web/src/lib/pluralize.ts delete mode 100644 web/src/lib/prisma.ts delete mode 100644 web/src/lib/redirectUrls.ts delete mode 100644 web/src/lib/redis/redisActionsSessions.ts delete mode 100644 web/src/lib/redis/redisGenericManager.ts delete mode 100644 web/src/lib/redis/redisImpersonationSessions.ts delete mode 100644 web/src/lib/redis/redisPreGeneratedSecretTokens.ts delete mode 100644 web/src/lib/redis/redisSessions.ts delete mode 100644 web/src/lib/schema.ts delete mode 100644 web/src/lib/sortSeed.ts delete mode 100644 web/src/lib/strings.ts delete mode 100644 web/src/lib/timeAgo.ts delete mode 100644 web/src/lib/timeTrapSecret.ts delete mode 100644 web/src/lib/urls.ts delete mode 100644 web/src/lib/userCookies.ts delete mode 100644 web/src/lib/userSecretToken.ts delete mode 100644 web/src/lib/zodUtils.ts delete mode 100644 web/src/middleware.ts delete mode 100644 web/src/pages/404.astro delete mode 100644 web/src/pages/500.astro delete mode 100644 web/src/pages/about.md delete mode 100644 web/src/pages/access-denied.astro delete mode 100644 web/src/pages/account/edit.astro delete mode 100644 web/src/pages/account/generate.astro delete mode 100644 web/src/pages/account/impersonate.astro delete mode 100644 web/src/pages/account/index.astro delete mode 100644 web/src/pages/account/login.astro delete mode 100644 web/src/pages/account/logout.astro delete mode 100644 web/src/pages/account/welcome.astro delete mode 100644 web/src/pages/admin/attributes.astro delete mode 100644 web/src/pages/admin/comments.astro delete mode 100644 web/src/pages/admin/index.astro delete mode 100644 web/src/pages/admin/service-suggestions/[id].astro delete mode 100644 web/src/pages/admin/service-suggestions/index.astro delete mode 100644 web/src/pages/admin/services/[slug]/edit.astro delete mode 100644 web/src/pages/admin/services/index.astro delete mode 100644 web/src/pages/admin/services/new.astro delete mode 100644 web/src/pages/admin/users/[username].astro delete mode 100644 web/src/pages/admin/users/index.astro delete mode 100644 web/src/pages/attributes.astro delete mode 100644 web/src/pages/events.astro delete mode 100644 web/src/pages/files/[...path].ts delete mode 100644 web/src/pages/index.astro delete mode 100644 web/src/pages/karma.mdx delete mode 100644 web/src/pages/notifications.astro delete mode 100644 web/src/pages/ogimage.png.ts delete mode 100644 web/src/pages/service-suggestion/[id].astro delete mode 100644 web/src/pages/service-suggestion/edit.astro delete mode 100644 web/src/pages/service-suggestion/index.astro delete mode 100644 web/src/pages/service-suggestion/new.astro delete mode 100644 web/src/pages/service/[slug].astro delete mode 100644 web/src/pages/u/[username].astro delete mode 100644 web/src/robots.txt.ts delete mode 100644 web/src/styles/global.css delete mode 100644 web/src/types/eslint-plugin-import.d.ts delete mode 100644 web/tsconfig.json diff --git a/.clinerules b/.clinerules deleted file mode 100644 index 1063fcd..0000000 --- a/.clinerules +++ /dev/null @@ -1,53 +0,0 @@ -# Cursor Rules - -- When merging tailwind classes, use the `cn` function. -- When using Tailwind and you need to merge classes use the `cn` function if avilable. -- We use Tailwind 4 (the latest version), make sure to not use outdated classes. -- Instead of using the syntax`Array`, use `T[]`. -- Use TypeScript `type` over `interface`. -- You are forbiddent o add comments unless explicitly stated by the user. -- Avoid sending JavaScript to the client. The JS send should be optional. -- In prisma preffer `select` over `include` when making queries. -- Import the types from prisma instead of hardcoding duplicates. -- Avoid duplicating similar html code, and parametrize it when possible or create separate components. -- Remember to check the prisma schema when doing things related to the database. -- Avoid hardcoding enums from the database, import them from prisma. -- Avoid using client-side JavaScript as much as possible. And if it has to be done, make it optional. -- The admin pages can use client-side JavaScript. -- Keep README.md in sync with new capabilities. -- The package manager is npm. -- For icons use the `Icon` component from `astro-icon/components`. -- For icons use the Remix Icon library preferably. -- Use the `Image` component from `astro:assets` for images. -- Use the `zod` library for schema validation. -- In the astro actions return, don't return success: true, or similar, just return an object with the newly created/edited objects or nothing. -- When adding actions, don't create and export a new variable called actions. Notice that Astro already provides that variable from `import { actions } from 'astro:actions'`. So just add the new actions to the `server` variable in `web/src/actions/index.ts` and that's it. -- Don't forget that the astro files have thre dashes (`---`) at the begining of the file and where the server js ends. I noticed that sometimes you forget them. -- The admin actions go into a separate folder. -- In Actro actions when throwing errors use ActionError. -- @deprecated Don't import this object, use {@link actions} instead, like: `import { actions } from 'astro:actions'`. Example: - - ```ts - import { actions } from "astro:actions"; /* CORRECT */ - import { server } from "~/actions"; /* WRONG!!!! DON'T DO THIS */ - import { adminAttributeActions } from "~/actions/admin/attribute.ts"; /* WRONG!!!! DON'T DO THIS */ - - const result = Astro.getActionResult(actions.admin.attribute.create); - ``` - -- Always use Astro actions instead of with API routes or `if (Astro.request.method === "POST")`. -- When adding clientside js do it with HTMX. -- When adding HTMX, the layout component BaseLayout accepts a prop htmx to load it in that page. No need to use a cdn. -- When redirecting to login use the `makeLoginUrl` function from web/src/lib/redirectUrls.ts - - ```ts - function makeLoginUrl( - currentUrl: URL, - options: { - redirect?: URL | string | null; - error?: string | null; - logout?: boolean; - message?: string | null; - } = {} - ); - ``` diff --git a/.cursorrules b/.cursorrules deleted file mode 100644 index 41c5415..0000000 --- a/.cursorrules +++ /dev/null @@ -1,307 +0,0 @@ -# Cursor Rules - -- When merging tailwind classes, use the `cn` function. -- When using Tailwind and you need to merge classes use the `cn` function if avilable. -- We use Tailwind 4 (the latest version), make sure to not use outdated classes. -- Instead of using the syntax`Array`, use `T[]`. -- Use TypeScript `type` over `interface`. -- You are forbiddent o add comments unless explicitly stated by the user. -- Avoid sending JavaScript to the client. The JS send should be optional. -- In prisma preffer `select` over `include` when making queries. -- Import the types from prisma instead of hardcoding duplicates. -- Avoid duplicating similar html code, and parametrize it when possible or create separate components. -- Remember to check the prisma schema when doing things related to the database. -- Avoid hardcoding enums from the database, import them from prisma. -- Avoid using client-side JavaScript as much as possible. And if it has to be done, make it optional. -- The admin pages can use client-side JavaScript. -- Keep README.md in sync with new capabilities. -- The package manager is npm. -- For icons use the `Icon` component from `astro-icon/components`. -- For icons use the Remix Icon library preferably. -- Use the `Image` component from `astro:assets` for images. -- Use the `zod` library for schema validation. -- In the astro actions return, don't return success: true, or similar, just return an object with the newly created/edited objects or nothing. -- When adding actions, don't create and export a new variable called actions. Notice that Astro already provides that variable from `import { actions } from 'astro:actions'`. So just add the new actions to the `server` variable in `web/src/actions/index.ts` and that's it. -- Don't forget that the astro files have three dashes (`---`) at the begining of the file and where the server js ends. I noticed that sometimes you forget them. -- The admin actions go into a separate folder. -- In Actro actions when throwing errors use ActionError. -- @deprecated Don't import this object, use {@link actions} instead, like: `import { actions } from 'astro:actions'`. Example: - - ```ts - import { actions } from 'astro:actions'; /* CORRECT */ - import { server } from '~/actions'; /* WRONG!!!! DON'T DO THIS */ - import { adminAttributeActions } from '~/actions/admin/attribute.ts'; /* WRONG!!!! DON'T DO THIS */ - - const result = Astro.getActionResult(actions.admin.attribute.create); - ``` - -- Always use Astro actions instead of with API routes or `if (Astro.request.method === "POST")`. -- When adding clientside js do it with HTMX. -- When adding HTMX, the layout component BaseLayout accepts a prop htmx to load it in that page. No need to use a cdn. -- When redirecting to login use the `makeLoginUrl` function from web/src/lib/redirectUrls.ts and if the link is for an `` tag, use the `data-astro-reload` attribute. - - ```ts - function makeLoginUrl( - currentUrl: URL, - options: { - redirect?: URL | string | null; - error?: string | null; - logout?: boolean; - message?: string | null; - } = {} - ); - ``` - -- When adding client scripts remember to use the event `astro:page-load`, `querySelectorAll` and add an explanation comment, like so: - - ```tsx - - ``` - -- When creating forms, we already have utilities, components and established design patterns. Follow this example: - - ```astro - --- - import { actions, isInputError } from 'astro:actions' - import { z } from 'astro:content' - - import Captcha from '../../components/Captcha.astro' - import InputCardGroup from '../../components/InputCardGroup.astro' - import InputCheckboxGroup from '../../components/InputCheckboxGroup.astro' - import InputHoneypotTrap from '../../components/InputHoneypotTrap.astro' - import InputImageFile from '../../components/InputImageFile.astro' - import InputSubmitButton from '../../components/InputSubmitButton.astro' - import InputText from '../../components/InputText.astro' - import InputTextArea from '../../components/InputTextArea.astro' - import { kycLevels } from '../../constants/kycLevels' - import BaseLayout from '../../layouts/BaseLayout.astro' - import { zodParseQueryParamsStoringErrors } from '../../lib/parseUrlFilters' - import { prisma } from '../../lib/prisma' - import { makeLoginUrl } from '../../lib/redirectUrls' - - const user = Astro.locals.user - if (!user) { - return Astro.redirect(makeLoginUrl(Astro.url, { message: 'Login to suggest a new service' })) - } - - const result = Astro.getActionResult(actions.serviceSuggestion.editService) - if (result && !result.error) { - return Astro.redirect(`/service-suggestion/${result.data.serviceSuggestion.id}`) - } - const inputErrors = isInputError(result?.error) ? result.error.fields : {} - - const { data: params } = zodParseQueryParamsStoringErrors( - { - serviceId: z.coerce.number().int().positive(), - notes: z.string().default(''), - }, - Astro - ) - - if (!params.serviceId) return Astro.rewrite('/404') - - const service = await Astro.locals.banners.try( - 'Failed to fetch service', - async () => - prisma.service.findUnique({ - select: { - id: true, - name: true, - slug: true, - description: true, - overallScore: true, - kycLevel: true, - imageUrl: true, - verificationStatus: true, - acceptedCurrencies: true, - categories: { - select: { - name: true, - icon: true, - }, - }, - }, - where: { id: params.serviceId }, - }), - null - ) - - if (!service) return Astro.rewrite('/404') - --- - - -

Edit service

- -
- - - - - ({ - label: kycLevel.name, - value: kycLevel.id.toString(), - icon: kycLevel.icon, - description: `${kycLevel.description}\n\n_KYC Level ${kycLevel.value}/5_`, - }))} - iconSize="md" - cardSize="md" - required - error={inputErrors.kycLevel} - /> - - ({ - label: category.name, - value: category.id.toString(), - icon: category.icon, - }))} - error={inputErrors.categories} - /> - - - - - - - - - - - -
- ``` - -- Don't use the `web/src/pages/admin` pages as example unless explicitly stated or you're creating/editing an admin page. -- When creating constants or enums, use the `makeHelpersForOptions` function like in this example. Save the file in the `web/src/constants` folder. Note that it's not necessary to use all the options the example has, just the ones you need. - -```ts -import { makeHelpersForOptions } from '../lib/makeHelpersForOptions'; -import { transformCase } from '../lib/strings'; - -import type { AttributeType } from '@prisma/client'; - -type AttributeTypeInfo = { - value: T; - slug: string; - label: string; - icon: string; - order: number; - classNames: { - text: string; - icon: string; - }; -}; - -export const { - dataArray: attributeTypes, - dataObject: attributeTypesById, - getFn: getAttributeTypeInfo, - getFnSlug: getAttributeTypeInfoBySlug, - zodEnumBySlug: attributeTypesZodEnumBySlug, - zodEnumById: attributeTypesZodEnumById, - keyToSlug: attributeTypeIdToSlug, - slugToKey: attributeTypeSlugToId, -} = makeHelpersForOptions( - 'value', - (value): AttributeTypeInfo => ({ - value, - slug: value ? value.toLowerCase() : '', - label: value - ? transformCase(value.replace('_', ' '), 'title') - : String(value), - icon: 'ri:question-line', - order: Infinity, - classNames: { - text: 'text-current/60', - icon: 'text-current/60', - }, - }), - [ - { - value: 'BAD', - slug: 'bad', - label: 'Bad', - icon: 'ri:close-line', - order: 1, - classNames: { - text: 'text-red-200', - icon: 'text-red-400', - }, - }, - { - value: 'WARNING', - slug: 'warning', - label: 'Warning', - icon: 'ri:alert-line', - order: 2, - classNames: { - text: 'text-yellow-200', - icon: 'text-yellow-400', - }, - }, - { - value: 'GOOD', - slug: 'good', - label: 'Good', - icon: 'ri:check-line', - order: 3, - classNames: { - text: 'text-green-200', - icon: 'text-green-400', - }, - }, - { - value: 'INFO', - slug: 'info', - label: 'Info', - icon: 'ri:information-line', - order: 4, - classNames: { - text: 'text-blue-200', - icon: 'text-blue-400', - }, - }, - ] as const satisfies AttributeTypeInfo[] -); -``` diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 54e225b..0000000 --- a/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -local_data/ -TODO.md -webhook -docker-compose.override.yml - -web/public/uploads/ -.env -backups/ -loki* -grafana* -dump*.sql -*.dump -*.log -*.bak -migrate.py \ No newline at end of file diff --git a/.npmrc b/.npmrc deleted file mode 100644 index cffe8cd..0000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -save-exact=true diff --git a/.platform/README.md b/.platform/README.md deleted file mode 100644 index 615c72b..0000000 --- a/.platform/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# .platform Hooks - -This directory contains deployment hooks that are executed during the deployment process. The structure follows AWS Elastic Beanstalk's `.platform` hooks pattern, although we are not using AWS we think it is a good practice to use this standard. - -## Directory Structure - -``` -.platform/ -├── hooks/ -│ ├── predeploy/ # Scripts executed before staging deployment -│ └── postdeploy/ # Scripts executed after successful production deployment -``` - -## Hook Execution - -- Scripts in each hook directory are executed in alphabetical order -- If any hook fails (returns non-zero), the deployment process is aborted -- Hook failures are reported through the notification system - -## Available Hooks - -### Predeploy Hooks - -Located in `.platform/hooks/predeploy/` - -- Executed before the staging deployment starts -- Use for tasks like: - - Environment validation - - Resource preparation - - Database migrations - - Asset compilation - -### Postdeploy Hooks - -Located in `.platform/hooks/postdeploy/` - -- Executed after successful production deployment -- Use for tasks like: - - Cache warming - - Service notifications - - Cleanup operations - - Import triggers (current implementation) - -## Example Hook - -```bash -#!/bin/bash -# .platform/hooks/postdeploy/01_import_triggers.sh - -cd ../../../ -just import-triggers -``` - -## Environment - -Hooks have access to all environment variables available to the deployment script, including: - -- `HOOK_PUSHER` -- `HOOK_MESSAGE` -- `GITEA_USERNAME` -- `GITEA_TOKEN` -- `GITEA_SERVER` -- `GITEA_REPO_USERNAME` -- `GITEA_REPO_NAME` diff --git a/.platform/hooks/postdeploy/01_import_triggers.sh b/.platform/hooks/postdeploy/01_import_triggers.sh deleted file mode 100644 index 4f25622..0000000 --- a/.platform/hooks/postdeploy/01_import_triggers.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -pwd -just import-triggers \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 908dc5d..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "recommendations": [ - "astro-build.astro-vscode", - "esbenp.prettier-vscode", - "dbaeumer.vscode-eslint", - "davidanson.vscode-markdownlint", - "golang.go", - "bradlc.vscode-tailwindcss", - "craigrbroughton.htmx-attributes", - "nefrob.vscode-just-syntax" - ], - "unwantedRecommendations": [] -} diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 51fbc35..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "npm run dev", - "request": "launch", - "type": "node-terminal", - "cwd": "${workspaceFolder}/web", - "command": "npm run dev" - } - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 0adfe61..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "editor.formatOnSave": true, - "editor.tabSize": 2, - "editor.insertSpaces": true, - "editor.wordWrap": "wordWrapColumn", - "editor.wordWrapColumn": 110, - "editor.rulers": [110], - "prettier.documentSelectors": ["**/*.astro"], - "[javascript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[typescript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[astro]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[markdown]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[json]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[yaml]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[prisma]": { - "editor.wordWrap": "off" - }, - "files.exclude": { - "**/node_modules": true - }, - "eslint.validate": [ - "javascript", - "javascriptreact", - "astro", - "typescript", - "typescriptreact" - ], - "editor.codeActionsOnSave": { - "source.fixAll": "explicit", - "source.organizeImports": "never", - "source.fixAll.eslint": "explicit" - }, - "eslint.enable": true, - "typescript.preferences.importModuleSpecifier": "non-relative", - "debug.javascript.autoAttachFilter": "always", - "tailwindCSS.classAttributes": [ - "class", - "className", - "classNames", - "ngClass", - "class:list", - ".*classNames?" - ], - "tailwindCSS.classFunctions": ["tv", "cn"], - "tailwindCSS.experimental.classRegex": [ - ["([\"'`][^\"'`]*.*?[\"'`])", "[\"'`]([^\"'`]*).*?[\"'`]"] - ] -} diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 0bddd6f..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "install", - "type": "shell", - "command": "cd web && npm i", - "icon": { - "id": "package", - "color": "terminal.ansiGreen" - }, - "detail": "Install npm dependencies" - }, - { - "label": "web", - "type": "shell", - "command": "cd web && npm run dev", - "icon": { - "id": "browser", - "color": "terminal.ansiBlue" - }, - "detail": "Start web development server", - "problemMatcher": ["$tsc-watch"], - "isBackground": true - }, - { - "label": "db", - "type": "shell", - "command": "docker compose -f docker-compose.yml -f docker-compose.dev.yml up database redis db-admin", - "runOptions": { - "runOn": "folderOpen" - }, - "icon": { - "id": "database", - "color": "terminal.ansiYellow" - }, - "detail": "Start database services" - }, - { - "label": "Install and run", - "dependsOrder": "sequence", - "dependsOn": ["install", "web"], - "runOptions": { - "runOn": "folderOpen" - }, - "icon": { - "id": "play", - "color": "terminal.ansiMagenta" - }, - "detail": "Setup and launch development environment" - } - ] -} diff --git a/README.md b/README.md deleted file mode 100644 index 7f465ff..0000000 --- a/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# KYCnot.me - -[KYCnot.me](https://kycnot.me) - -## Development - -### Installations - -Install the following tools: - -- [nvm](https://github.com/nvm-sh/nvm) (or [node](https://nodejs.org/en/download/)) -- [docker](https://docs.docker.com/get-docker/) -- [just](https://just.systems) - -### Initialization - -Run this the first time you setup the project: - -```zsh -# you can alternatively use `just dev-database` -docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --wait database redis -cd web -nvm install -npm i -cp -n .env.example .env -npm run db-push -npm run db-fill-clean -``` - -Now open the [.env](web/.env) file and fill in the missing values. - -> Default users are created with tokens: `admin`, `verifier`, `verified`, `normal` (configurable via env vars) - -### Running the project - -In separate terminals, run the following commands: - -- Database - - ```zsh - # you can alternatively use `just dev-database` - docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --wait database redis - ``` - -- Website - - ```zsh - cd web - nvm use - npm run dev - ``` - -- Database Admin (Optional) - - ```zsh - cd web - nvm use - npm run db-admin - ``` - -> [!TIP] -> VS Code will run the project in development mode automatically when you open the project. diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml deleted file mode 100644 index d14fad1..0000000 --- a/docker-compose.dev.yml +++ /dev/null @@ -1,45 +0,0 @@ -services: - database: - volumes: - - ./local_data/postgres:/var/lib/postgresql/data:z - ports: - - 3399:5432 - restart: no - environment: - POSTGRES_USER: kycnot - POSTGRES_PASSWORD: kycnot - POSTGRES_DB: kycnot - healthcheck: - test: ["CMD-SHELL", "pg_isready -U kycnot -d kycnot"] - interval: 10s - timeout: 5s - retries: 5 - - db-admin: - image: node:20 - working_dir: /app - volumes: - - ./web:/app - restart: unless-stopped - environment: - POSTGRES_USER: ${POSTGRES_USER:-kycnot} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-kycnot} - POSTGRES_DB: ${POSTGRES_DATABASE:-kycnot} - DATABASE_URL: "postgresql://${POSTGRES_USER:-kycnot}:${POSTGRES_PASSWORD:-kycnot}@database:5432/${POSTGRES_DATABASE:-kycnot}?schema=public" - depends_on: - database: - condition: service_healthy - expose: - - 5555 - ports: - - "5555:5555" - command: ["npm", "run", "db-admin"] - healthcheck: - test: ["CMD", "curl", "-k", "--silent", "--fail", "http://localhost:5555"] - interval: 10s - timeout: 5s - retries: 5 - - redis: - ports: - - "6379:6379" diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index c86b674..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,76 +0,0 @@ -volumes: - database: - -services: - database: - image: postgres:latest - volumes: - - database:/var/lib/postgresql/data:z - restart: unless-stopped - environment: - POSTGRES_USER: ${POSTGRES_USER:-kycnot} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-kycnot} - POSTGRES_DB: ${POSTGRES_DATABASE:-kycnot} - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-kycnot} -d ${POSTGRES_DATABASE:-kycnot}"] - interval: 10s - timeout: 5s - retries: 5 - - pyworker: - build: - context: ./pyworker - restart: always - environment: - DATABASE_URL: "postgresql://${POSTGRES_USER:-kycnot}:${POSTGRES_PASSWORD:-kycnot}@database:5432/${POSTGRES_DATABASE:-kycnot}?schema=public" - CRAWL4AI_BASE_URL: "http://crawl4ai:11235" - CRAWL4AI_API_TOKEN: ${CRAWL4AI_API_TOKEN:-testing} - - crawl4ai: - image: unclecode/crawl4ai:basic-amd64 - expose: - - "11235" - environment: - CRAWL4AI_API_TOKEN: ${CRAWL4AI_API_TOKEN:-testing} # Optional API security - MAX_CONCURRENT_TASKS: 10 - volumes: - - /dev/shm:/dev/shm - deploy: - resources: - limits: - memory: 4G - reservations: - memory: 1G - - redis: - image: redis:latest - restart: unless-stopped - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 10s - timeout: 5s - retries: 5 - - astro: - build: - context: ./web - image: kycnotme/astro:${ASTRO_IMAGE_TAG:-latest} - restart: unless-stopped - environment: - POSTGRES_USER: ${POSTGRES_USER:-kycnot} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-kycnot} - POSTGRES_DB: ${POSTGRES_DATABASE:-kycnot} - DATABASE_URL: "postgresql://${POSTGRES_USER:-kycnot}:${POSTGRES_PASSWORD:-kycnot}@database:5432/${POSTGRES_DATABASE:-kycnot}?schema=public" - REDIS_URL: "redis://redis:6379" - depends_on: - database: - condition: service_healthy - redis: - condition: service_healthy - expose: - - 4321 - healthcheck: - test: ["CMD", "curl", "-k", "--silent", "--fail", "http://localhost:4321"] - interval: 10s - timeout: 5s - retries: 5 diff --git a/justfile b/justfile deleted file mode 100644 index 33a74f8..0000000 --- a/justfile +++ /dev/null @@ -1,50 +0,0 @@ -set dotenv-load - -@default: - just --list - -# Start the development database and redis services -dev-database: - docker compose -f docker-compose.yml -f docker-compose.dev.yml up database redis db-admin - -# Import all triggers to the database -import-triggers: - #!/bin/bash - for sql_file in web/prisma/triggers/*.sql; do - echo "Importing $sql_file..." - docker compose exec -T database psql -U ${DATABASE_USER:-kycnot} -d ${DATABASE_NAME:-kycnot} < "$sql_file" - done - -dump-db: - #!/bin/bash - mkdir -p backups - TIMESTAMP=$(date +%Y%m%d_%H%M%S) - echo "Creating database backup (excluding _prisma_migrations table)..." - docker compose exec -T database pg_dump -U ${POSTGRES_USER:-kycnot} -d ${POSTGRES_DATABASE:-kycnot} -c -F c -T _prisma_migrations > backups/db_backup_${TIMESTAMP}.dump - echo "Backup saved to backups/db_backup_${TIMESTAMP}.dump" - -# Import a database backup. Usage: just import-db [filename] -# If no filename is provided, it will use the most recent backup -import-db file="": - #!/bin/bash - if [ -z "{{file}}" ]; then - BACKUP_FILE=$(find backups/ -name 'db_backup_*.dump' | sort -r | head -n 1) - if [ -z "$BACKUP_FILE" ]; then - echo "Error: No backup files found in the backups directory" - exit 1 - fi - else - BACKUP_FILE="{{file}}" - if [ ! -f "$BACKUP_FILE" ]; then - echo "Error: Backup file '$BACKUP_FILE' not found" - exit 1 - fi - fi - - echo "Restoring database from $BACKUP_FILE..." - # First drop all connections to the database - docker compose exec -T database psql -U ${POSTGRES_USER:-kycnot} -c "SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '${POSTGRES_DATABASE:-kycnot}' AND pid <> pg_backend_pid();" postgres - # Then restore the database - cat "$BACKUP_FILE" | docker compose exec -T database pg_restore -U ${POSTGRES_USER:-kycnot} -d ${POSTGRES_DATABASE:-kycnot} --clean --if-exists - echo "Database restored successfully!" - \ No newline at end of file diff --git a/pyworker/.env.example b/pyworker/.env.example deleted file mode 100644 index 012ad1b..0000000 --- a/pyworker/.env.example +++ /dev/null @@ -1,21 +0,0 @@ -# Database connection -DATABASE_URL=postgresql://kycnot:kycnot@localhost:3399/kycnot - -# API settings -TOS_API_BASE_URL=https://r.jina.ai - -# Logging -LOG_LEVEL=INFO -LOG_FORMAT=%(asctime)s - %(name)s - %(levelname)s - %(message)s - -# OpenAI -OPENAI_API_KEY="xxxxxxxxx" -OPENAI_BASE_URL="https://xxxxxx/api/v1" -OPENAI_MODEL="xxxxxxxxx" -OPENAI_RETRY=3 - -CRON_TOSREVIEW_TASK=0 0 1 * * # Every month -CRON_USER_SENTIMENT_TASK=0 0 * * * # Every day -CRON_COMMENT_MODERATION_TASK=0 0 * * * # Every hour -CRON_FORCE_TRIGGERS_TASK=0 2 * * * # Every day -CRON_SERVICE_SCORE_RECALC_TASK=*/5 * * * * # Every 10 minutes \ No newline at end of file diff --git a/pyworker/.gitignore b/pyworker/.gitignore deleted file mode 100644 index 0a19790..0000000 --- a/pyworker/.gitignore +++ /dev/null @@ -1,174 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# UV -# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -#uv.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/latest/usage/project/#working-with-version-control -.pdm.toml -.pdm-python -.pdm-build/ - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ - -# Ruff stuff: -.ruff_cache/ - -# PyPI configuration file -.pypirc diff --git a/pyworker/.python-version b/pyworker/.python-version deleted file mode 100644 index 24ee5b1..0000000 --- a/pyworker/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.13 diff --git a/pyworker/Dockerfile b/pyworker/Dockerfile deleted file mode 100644 index d6cc491..0000000 --- a/pyworker/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM ghcr.io/astral-sh/uv:alpine - -WORKDIR /app - -COPY . . - -RUN uv sync --frozen - -EXPOSE 8000 -CMD ["uv", "run", "-m", "pyworker", "--worker"] diff --git a/pyworker/README.md b/pyworker/README.md deleted file mode 100644 index bba9c6e..0000000 --- a/pyworker/README.md +++ /dev/null @@ -1,149 +0,0 @@ -# KYC Not Worker - -A Python worker for processing and analyzing data for the KYC Not project. - -## Features - -- TOS (Terms of Service) text retrieval and analysis -- User sentiment analysis from comments -- Comment moderation -- Service score recalculation -- Database trigger maintenance -- Scheduled task execution -- Database operations for services and comments - -## Installation - -1. Clone the repository -2. Sync dependencies with [uv](https://docs.astral.sh/uv/): - - ```bash - uv sync - ``` - -## Configuration - -Copy `.env.example` to `.env` and fill in the required values: - -```bash -cp .env.example .env -``` - -Required environment variables: - -- `DATABASE_URL`: PostgreSQL connection string -- `OPENAI_API_KEY`: OpenAI API key for AI tasks -- `CRON_TOSREVIEW_TASK`: Cron expression for TOS review task -- `CRON_SENTIMENT_TASK`: Cron expression for user sentiment analysis task -- `CRON_MODERATION_TASK`: Cron expression for comment moderation task -- `CRON_FORCE_TRIGGERS_TASK`: Cron expression for force triggers task -- `CRON_SERVICE_SCORE_RECALC_TASK`: Cron expression for service score recalculation task - -## Usage - -### Command Line Interface - -Run tasks directly: - -```bash -# Run TOS review task -uv run -m pyworker tos [--service-id ID] - -# Run user sentiment analysis task -uv run -m pyworker sentiment [--service-id ID] - -# Run comment moderation task -uv run -m pyworker moderation [--service-id ID] - -# Run force triggers task -uv run -m pyworker force-triggers - -# Run service score recalculation task -uv run -m pyworker service-score-recalc [--service-id ID] -``` - -### Worker Mode - -Run in worker mode to execute tasks on a schedule: - -```bash -uv run -m pyworker --worker -``` - -Tasks will run according to their configured cron schedules. - -## Tasks - -### TOS Review Task - -- Retrieves and analyzes Terms of Service documents -- Updates service records with TOS information -- Scheduled via `CRON_TOSREVIEW_TASK` - -### User Sentiment Task - -- Analyzes user comments to determine overall sentiment -- Updates service records with sentiment analysis -- Scheduled via `CRON_SENTIMENT_TASK` - -### Comment Moderation Task - -- Makes a basic first moderation of comments -- Flags comments as needed -- Adds content if needed -- Scheduled via `CRON_MODERATION_TASK` - -### Force Triggers Task - -- Maintains database triggers by forcing them to run under certain conditions -- Currently handles updating the "isRecentlyListed" flag for services after 15 days -- Scheduled via `CRON_FORCE-TRIGGERS_TASK` - -### Service Score Recalculation Task - -- Recalculates service scores based on attribute changes -- Processes jobs from the ServiceScoreRecalculationJob table -- Calculates privacy, trust, and overall scores -- Scheduled via `CRON_SERVICE-SCORE-RECALC_TASK` - -## Development - -### Project Structure - -```text -pyworker/ -├── pyworker/ -│ ├── __init__.py -│ ├── __main__.py -│ ├── cli.py -│ ├── config.py -│ ├── database.py -│ ├── scheduler.py -│ ├── tasks/ -│ │ ├── __init__.py -│ │ ├── base.py -│ │ ├── comment_moderation.py -│ │ ├── force_triggers.py -│ │ ├── service_score_recalc.py -│ │ ├── tos_review.py -│ │ └── user_sentiment.py -│ └── utils/ -│ ├── __init__.py -│ ├── ai.py -│ └── logging.py -├── tests/ -├── setup.py -├── requirements.txt -└── README.md -``` - -### Adding New Tasks - -1. Create a new task class in `pyworker/tasks/` -2. Implement the `run` method -3. Add the task to `pyworker/tasks/__init__.py` -4. Update the CLI and scheduler to handle the new task - -## License - -MIT diff --git a/pyworker/docker-compose.yml b/pyworker/docker-compose.yml deleted file mode 100644 index 5af72ee..0000000 --- a/pyworker/docker-compose.yml +++ /dev/null @@ -1,12 +0,0 @@ -services: - pyworker: - build: - context: . - dockerfile: Dockerfile - restart: always - env_file: - - .env - environment: - - OPENAI_API_KEY=${OPENAI_API_KEY} - - OPENAI_MODEL=${OPENAI_MODEL} - - DATABASE_URL=${DATABASE_URL} diff --git a/pyworker/pyproject.toml b/pyworker/pyproject.toml deleted file mode 100644 index 42e20cd..0000000 --- a/pyworker/pyproject.toml +++ /dev/null @@ -1,24 +0,0 @@ -[project] -name = "pyworker" -version = "0.1.0" -description = "AI workers for kycnot.me" -readme = "README.md" -requires-python = ">=3.13" -dependencies = [ - "croniter>=6.0.0", - "json-repair>=0.41.1", - "openai>=1.74.0", - "psycopg[binary,pool]>=3.2.6", - "python-dotenv>=1.1.0", - "requests>=2.32.3", -] - -[project.scripts] -pyworker = "pyworker.cli:main" - -[tool.setuptools] -packages = ["pyworker"] - -[build-system] -requires = ["setuptools>=61.0"] -build-backend = "setuptools.build_meta" diff --git a/pyworker/pyworker/__init__.py b/pyworker/pyworker/__init__.py deleted file mode 100644 index f34beef..0000000 --- a/pyworker/pyworker/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -KYC Not Worker Package - -A package for worker tasks related to the KYC Not platform. -""" - -__version__ = "0.1.0" \ No newline at end of file diff --git a/pyworker/pyworker/__main__.py b/pyworker/pyworker/__main__.py deleted file mode 100644 index e03d561..0000000 --- a/pyworker/pyworker/__main__.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python3 -""" -Entry point for the pyworker package when executed as a module. -""" - -import sys -from pyworker.cli import main - -if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file diff --git a/pyworker/pyworker/cli.py b/pyworker/pyworker/cli.py deleted file mode 100644 index a8d5c01..0000000 --- a/pyworker/pyworker/cli.py +++ /dev/null @@ -1,437 +0,0 @@ -""" -Command line interface for the pyworker package. -""" - -import argparse -import sys -import time -from typing import List, Optional, Dict, Any - -from pyworker.config import config -from pyworker.database import ( - close_db_pool, - fetch_all_services, - fetch_services_with_pending_comments, -) -from pyworker.scheduler import TaskScheduler -from .tasks import ( - CommentModerationTask, - ForceTriggersTask, - ServiceScoreRecalculationTask, - TosReviewTask, - UserSentimentTask, -) -from pyworker.utils.app_logging import setup_logging - -logger = setup_logging(__name__) - - -def parse_args(args: List[str]) -> argparse.Namespace: - """ - Parse command line arguments. - - Args: - args: Command line arguments. - - Returns: - Parsed arguments. - """ - parser = argparse.ArgumentParser(description="KYC Not Worker") - - # Global options - parser.add_argument( - "--worker", - action="store_true", - help="Run in worker mode (schedule tasks to run periodically)", - ) - - # Add subparsers for different tasks - subparsers = parser.add_subparsers(dest="task", help="Task to run") - - # TOS retrieval task - tos_parser = subparsers.add_parser( - "tos", help="Retrieve Terms of Service (TOS) text" - ) - tos_parser.add_argument( - "--service-id", type=int, help="Specific service ID to process (optional)" - ) - - # User sentiment task - sentiment_parser = subparsers.add_parser( - "sentiment", help="Analyze user sentiment from comments" - ) - sentiment_parser.add_argument( - "--service-id", type=int, help="Specific service ID to process (optional)" - ) - - # Comment moderation task - moderation_parser = subparsers.add_parser( - "moderation", help="Moderate pending comments" - ) - moderation_parser.add_argument( - "--service-id", type=int, help="Specific service ID to process (optional)" - ) - - # New Service Penalty task - penalty_parser = subparsers.add_parser( - "force-triggers", - help="Force triggers to run under certain conditions", - ) - penalty_parser.add_argument( - "--service-id", type=int, help="Specific service ID to process (optional)" - ) - - # Service Score Recalculation task - score_recalc_parser = subparsers.add_parser( - "service-score-recalc", - help="Recalculate service scores based on attribute changes", - ) - score_recalc_parser.add_argument( - "--service-id", type=int, help="Specific service ID to process (optional)" - ) - - return parser.parse_args(args) - - -def run_tos_task(service_id: Optional[int] = None) -> int: - """ - Run the TOS retrieval task. - - Args: - service_id: Optional specific service ID to process. - - Returns: - Exit code. - """ - logger.info("Starting TOS retrieval task") - - try: - # Fetch services - services = fetch_all_services() - if not services: - logger.error("No services found") - return 1 - - # Filter by service ID if specified - if service_id: - services = [s for s in services if s["id"] == service_id] - if not services: - logger.error(f"Service with ID {service_id} not found") - return 1 - - # Initialize task and use as context manager - with TosReviewTask() as task: # type: ignore - # Process services using the same database connection - for service in services: - if not service.get("tosUrls"): - logger.info( - f"Skipping service {service['name']} (ID: {service['id']}) - no TOS URLs" - ) - continue - - result = task.run(service) # type: ignore - if result: - logger.info( - f"Successfully retrieved TOS for service {service['name']}" - ) - else: - logger.warning( - f"Failed to retrieve TOS for service {service['name']}" - ) - - logger.info("TOS retrieval task completed") - return 0 - finally: - # Ensure connection pool is closed even if an error occurs - close_db_pool() - - -def run_sentiment_task(service_id: Optional[int] = None) -> int: - """ - Run the user sentiment analysis task. - - Args: - service_id: Optional specific service ID to process. - - Returns: - Exit code. - """ - logger.info("Starting user sentiment analysis task") - - try: - # Fetch services - services = fetch_all_services() - if not services: - logger.error("No services found") - return 1 - - # Filter by service ID if specified - if service_id: - services = [s for s in services if s["id"] == service_id] - if not services: - logger.error(f"Service with ID {service_id} not found") - return 1 - - # Initialize task and use as context manager - with UserSentimentTask() as task: # type: ignore - # Process services using the same database connection - for service in services: - result = task.run(service) # type: ignore - if result is not None: - logger.info( - f"Successfully analyzed sentiment for service {service['name']}" - ) - - logger.info("User sentiment analysis task completed") - return 0 - finally: - # Ensure connection pool is closed even if an error occurs - close_db_pool() - - -def run_moderation_task(service_id: Optional[int] = None) -> int: - """ - Run the comment moderation task. - - Args: - service_id: Optional specific service ID to process. - - Returns: - Exit code. - """ - logger.info("Starting comment moderation task") - - try: - services_to_process: List[Dict[str, Any]] = [] - if service_id: - # Fetch specific service if ID is provided - # Consider creating a fetch_service_by_id for efficiency if this path is common - all_services = fetch_all_services() - services_to_process = [s for s in all_services if s["id"] == service_id] - if not services_to_process: - logger.error( - f"Service with ID {service_id} not found or does not meet general fetch criteria." - ) - return 1 - logger.info(f"Processing specifically for service ID: {service_id}") - else: - # No specific service ID, fetch only services with pending comments - logger.info( - "No specific service ID provided. Querying for services with pending comments." - ) - services_to_process = fetch_services_with_pending_comments() - if not services_to_process: - logger.info( - "No services found with pending comments for moderation at this time." - ) - # Task completed its check, nothing to do. - # Fall through to common completion log. - - any_service_had_comments_processed = False - if not services_to_process and not service_id: - # This case is when no service_id was given AND no services with pending comments were found. - # Already logged above. - pass - elif not services_to_process and service_id: - # This case should have been caught by the 'return 1' if service_id was specified but not found. - # If it reaches here, it implies an issue or the service had no pending comments (which the task will handle). - logger.info( - f"Service ID {service_id} was specified, but no matching service found or it has no pending items for the task." - ) - else: - logger.info( - f"Identified {len(services_to_process)} service(s) to check for comment moderation." - ) - - # Initialize task and use as context manager - with CommentModerationTask() as task: # type: ignore - for service in services_to_process: - # The CommentModerationTask.run() method now returns a boolean - # and handles its own logging regarding finding/processing comments for the service. - if task.run(service): # type: ignore - logger.info( - f"Comment moderation task ran for service {service['name']} (ID: {service['id']}) and processed comments." - ) - any_service_had_comments_processed = True - else: - logger.info( - f"Comment moderation task ran for service {service['name']} (ID: {service['id']}), but no new comments were moderated." - ) - - if services_to_process and not any_service_had_comments_processed: - logger.info( - "Completed iterating through services; no comments were moderated in this run." - ) - - logger.info("Comment moderation task completed") - return 0 - finally: - # Ensure connection pool is closed even if an error occurs - close_db_pool() - - -def run_force_triggers_task() -> int: - """ - Runs the force triggers task. - - Returns: - Exit code. - """ - logger.info("Starting force triggers task") - - try: - # Initialize task and use as context manager - with ForceTriggersTask() as task: # type: ignore - success = task.run() # type: ignore - - if success: - logger.info("Force triggers task completed successfully.") - return 0 - else: - logger.error("Force triggers task failed.") - return 1 - finally: - # Ensure connection pool is closed even if an error occurs - close_db_pool() - - -def run_service_score_recalc_task(service_id: Optional[int] = None) -> int: - """ - Run the service score recalculation task. - - Args: - service_id: Optional specific service ID to process. - - Returns: - Exit code. - """ - logger.info("Starting service score recalculation task") - - try: - # Initialize task and use as context manager - with ServiceScoreRecalculationTask() as task: # type: ignore - result = task.run(service_id) # type: ignore - if result: - logger.info("Successfully recalculated service scores") - else: - logger.warning("Failed to recalculate service scores") - - logger.info("Service score recalculation task completed") - return 0 - finally: - # Ensure connection pool is closed even if an error occurs - close_db_pool() - - -def run_worker_mode() -> int: - """ - Run in worker mode, scheduling tasks to run periodically. - - Returns: - Exit code. - """ - logger.info("Starting worker mode") - - # Get task schedules from config - task_schedules = config.task_schedules - if not task_schedules: - logger.error( - "No task schedules defined. Set CRON_TASKNAME_TASK environment variables." - ) - return 1 - - logger.info( - f"Found {len(task_schedules)} scheduled tasks: {', '.join(task_schedules.keys())}" - ) - - # Initialize the scheduler - scheduler = TaskScheduler() - - # Register tasks with their schedules - for task_name, cron_expression in task_schedules.items(): - if task_name.lower() == "tosreview": - scheduler.register_task(task_name, cron_expression, run_tos_task) - elif task_name.lower() == "user_sentiment": - scheduler.register_task(task_name, cron_expression, run_sentiment_task) - elif task_name.lower() == "comment_moderation": - scheduler.register_task(task_name, cron_expression, run_moderation_task) - elif task_name.lower() == "force_triggers": - scheduler.register_task(task_name, cron_expression, run_force_triggers_task) - elif task_name.lower() == "service_score_recalc": - scheduler.register_task( - task_name, cron_expression, run_service_score_recalc_task - ) - else: - logger.warning(f"Unknown task '{task_name}', skipping") - - # Register service score recalculation task (every 5 minutes) - scheduler.register_task( - "service-score-recalc", - "*/5 * * * *", - run_service_score_recalc_task, - ) - - # Start the scheduler if tasks were registered - if scheduler.tasks: - try: - scheduler.start() - logger.info("Worker started, press Ctrl+C to stop") - - # Keep the main thread alive - while scheduler.is_running(): - time.sleep(1) - - return 0 - except KeyboardInterrupt: - logger.info("Keyboard interrupt received, shutting down...") - scheduler.stop() - return 0 - except Exception as e: - logger.exception(f"Error in worker mode: {e}") - scheduler.stop() - return 1 - else: - logger.error("No valid tasks registered") - return 1 - - -def main() -> int: - """ - Main entry point. - - Returns: - Exit code. - """ - args = parse_args(sys.argv[1:]) - - try: - # If worker mode is specified, run the scheduler - if args.worker: - return run_worker_mode() - - # Otherwise, run the specified task once - if args.task == "tos": - return run_tos_task(args.service_id) - elif args.task == "sentiment": - return run_sentiment_task(args.service_id) - elif args.task == "moderation": - return run_moderation_task(args.service_id) - elif args.task == "force-triggers": - return run_force_triggers_task() - elif args.task == "service-score-recalc": - return run_service_score_recalc_task(args.service_id) - elif args.task: - logger.error(f"Unknown task: {args.task}") - return 1 - else: - logger.error( - "No task specified. Use --worker for scheduled execution or specify a task to run once." - ) - return 1 - except Exception as e: - logger.exception(f"Error running task: {e}") - return 1 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/pyworker/pyworker/config.py b/pyworker/pyworker/config.py deleted file mode 100644 index 0b83f52..0000000 --- a/pyworker/pyworker/config.py +++ /dev/null @@ -1,67 +0,0 @@ -""" -Configuration module for pyworker. - -Handles loading environment variables and configuration settings. -""" - -import os -import re -from typing import Dict - -from dotenv import load_dotenv - -# Load environment variables from .env file -load_dotenv() - - -class Config: - """Configuration class for the worker application.""" - - # Database settings - DATABASE_URL: str = os.getenv( - "DATABASE_URL", "postgresql://kycnot:kycnot@localhost:3399/kycnot" - ) - - # Clean the URL by removing any query parameters - @property - def db_connection_string(self) -> str: - """Get the clean database connection string without query parameters.""" - if "?" in self.DATABASE_URL: - return self.DATABASE_URL.split("?")[0] - return self.DATABASE_URL - - # API settings - TOS_API_BASE_URL: str = os.getenv("TOS_API_BASE_URL", "https://r.jina.ai") - - # Logging settings - LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO") - LOG_FORMAT: str = os.getenv( - "LOG_FORMAT", "%(asctime)s - %(name)s - %(levelname)s - %(message)s" - ) - - # Task scheduling - @property - def task_schedules(self) -> Dict[str, str]: - """ - Get cron schedules for tasks from environment variables. - - Looks for environment variables with the pattern CRON_TASKNAME_TASK - and returns a dictionary mapping task names to cron schedules. - - Returns: - Dictionary mapping task names to cron schedules. - """ - schedules: Dict[str, str] = {} - cron_pattern = re.compile(r"^CRON_(\w+)_TASK$") - - for key, value in os.environ.items(): - match = cron_pattern.match(key) - if match: - task_name = match.group(1).lower() - schedules[task_name] = value - - return schedules - - -# Create a singleton instance -config = Config() diff --git a/pyworker/pyworker/database.py b/pyworker/pyworker/database.py deleted file mode 100644 index ab18eaf..0000000 --- a/pyworker/pyworker/database.py +++ /dev/null @@ -1,733 +0,0 @@ -""" -Database operations for the pyworker package. -""" - -import json -from contextlib import contextmanager -from datetime import datetime -from typing import Any, Dict, Generator, List, Optional, TypedDict, Union -from typing import Literal as TypeLiteral - -import psycopg -from psycopg.rows import dict_row -from psycopg.sql import SQL, Composed, Literal -from psycopg_pool import ConnectionPool # Proper import for the connection pool - -from pyworker.config import config -from pyworker.utils.app_logging import setup_logging - -logger = setup_logging(__name__) - -# --- Type Definitions --- - - -# Moved from tasks/comment_moderation.py -class CommentType(TypedDict): - id: int - upvotes: int - status: str # Assuming CommentStatus Enum isn't used across modules yet - suspicious: bool - requiresAdminReview: bool - communityNote: Optional[str] - internalNote: Optional[str] - privateContext: Optional[str] - content: str - rating: Optional[float] - createdAt: datetime - updatedAt: datetime - authorId: int - serviceId: int - parentId: Optional[int] - # Add author/service/reply fields if needed by update_comment - - -# Moved from utils/ai.py -RatingType = TypeLiteral["info", "warning", "alert"] - - -class UserRightType(TypedDict): - text: str - rating: RatingType - - -class DataSharingType(TypedDict): - text: str - rating: RatingType - - -class DataCollectedType(TypedDict): - text: str - rating: RatingType - - -class KycOrSourceOfFundsType(TypedDict): - text: str - rating: RatingType - - -class TosReviewType(TypedDict, total=False): - contentHash: str - kycLevel: int - summary: str - complexity: TypeLiteral["low", "medium", "high"] - highlights: List[Dict[str, Any]] - - -class CommentSentimentSummaryType(TypedDict): - summary: str - sentiment: TypeLiteral["positive", "negative", "neutral"] - whatUsersLike: List[str] - whatUsersDislike: List[str] - - -class CommentModerationType(TypedDict): - isSpam: bool - requiresAdminReview: bool - contextNote: str - internalNote: str - commentQuality: int - - -QueryType = Union[str, bytes, SQL, Composed, Literal] - - -# --- Database Connection Pool --- -_db_pool: Optional[ConnectionPool] = None - - -def get_db_pool() -> ConnectionPool: - """ - Get or create the database connection pool. - - Returns: - A connection pool object. - """ - global _db_pool - if _db_pool is None: - try: - # Create a new connection pool with min connections of 2 and max of 10 - _db_pool = ConnectionPool( - conninfo=config.db_connection_string, - min_size=2, - max_size=10, - # Configure how connections are initialized - kwargs={ - "autocommit": False, - }, - ) - logger.info("Database connection pool initialized") - except Exception as e: - logger.error(f"Error creating database connection pool: {e}") - raise - return _db_pool - - -def close_db_pool(): - """ - Close the database connection pool. - This should be called when the application is shutting down. - """ - global _db_pool - if _db_pool is not None: - logger.info("Closing database connection pool") - _db_pool.close() - _db_pool = None - - -@contextmanager -def get_db_connection() -> Generator[psycopg.Connection, None, None]: - """ - Context manager for database connections. - - Yields: - A database connection object from the pool. - """ - pool = get_db_pool() - try: - # Use the connection method which returns a connection as a context manager - with pool.connection() as conn: - # Set the schema explicitly after connection - with conn.cursor() as cursor: - cursor.execute("SET search_path TO public") - yield conn - # The connection will be automatically returned to the pool - # when the with block exits - except Exception as e: - logger.error(f"Error connecting to the database: {e}") - raise - - -# --- Database Functions --- - - -def fetch_all_services() -> List[Dict[str, Any]]: - """ - Fetch all public and verified services from the database. - - Returns: - A list of service dictionaries. - """ - services = [] - try: - with get_db_connection() as conn: - with conn.cursor(row_factory=dict_row) as cursor: - cursor.execute(""" - SELECT id, name, slug, description, "kycLevel", "overallScore", - "privacyScore", "trustScore", "verificationStatus", - "serviceVisibility", "tosUrls", "serviceUrls", "onionUrls", "i2pUrls", - "tosReview", "tosReviewAt", "userSentiment", "userSentimentAt" - FROM "Service" - WHERE "serviceVisibility" = 'PUBLIC' - AND ("verificationStatus" = 'VERIFICATION_SUCCESS' - OR "verificationStatus" = 'COMMUNITY_CONTRIBUTED' - OR "verificationStatus" = 'APPROVED') - ORDER BY id - """) - services = cursor.fetchall() - logger.info(f"Fetched {len(services)} services from the database") - except Exception as e: - logger.error(f"Error fetching services: {e}") - - return services - - -def fetch_services_with_pending_comments() -> List[Dict[str, Any]]: - """ - Fetch all public and verified services that have at least one pending comment. - - Returns: - A list of service dictionaries. - """ - services = [] - try: - with get_db_connection() as conn: - with conn.cursor(row_factory=dict_row) as cursor: - cursor.execute(""" - SELECT DISTINCT s.id, s.name, s.slug, s.description, s."kycLevel", s."overallScore", - s."privacyScore", s."trustScore", s."verificationStatus", - s."serviceVisibility", s."tosUrls", s."serviceUrls", s."onionUrls", s."i2pUrls", - s."tosReview", s."tosReviewAt", s."userSentiment", s."userSentimentAt" - FROM "Service" s - JOIN "Comment" c ON s.id = c."serviceId" - WHERE c.status = 'PENDING' - AND s."serviceVisibility" = 'PUBLIC' - AND (s."verificationStatus" = 'VERIFICATION_SUCCESS' - OR s."verificationStatus" = 'COMMUNITY_CONTRIBUTED' - OR s."verificationStatus" = 'APPROVED') - ORDER BY s.id - """) - services = cursor.fetchall() - logger.info( - f"Fetched {len(services)} services with pending comments from the database" - ) - except Exception as e: - logger.error(f"Error fetching services with pending comments: {e}") - - return services - - -def fetch_service_attributes(service_id: int) -> List[Dict[str, Any]]: - """ - Fetch attributes for a specific service. - - Args: - service_id: The ID of the service. - - Returns: - A list of attribute dictionaries. - """ - attributes = [] - try: - with get_db_connection() as conn: - with conn.cursor(row_factory=dict_row) as cursor: - cursor.execute( - """ - SELECT a.id, a.slug, a.title, a.description, a.category, a.type - FROM "Attribute" a - JOIN "ServiceAttribute" sa ON a.id = sa."attributeId" - WHERE sa."serviceId" = %s - """, - (service_id,), - ) - attributes = cursor.fetchall() - except Exception as e: - logger.error(f"Error fetching attributes for service {service_id}: {e}") - - return attributes - - -def get_attribute_id_by_slug(slug: str) -> Optional[int]: - attribute_id = None - try: - with get_db_connection() as conn: - with conn.cursor(row_factory=dict_row) as cursor: - cursor.execute('SELECT id FROM "Attribute" WHERE slug = %s', (slug,)) - row = cursor.fetchone() - if row: - attribute_id = row["id"] - except Exception as e: - logger.error(f"Error fetching attribute id for slug '{slug}': {e}") - return attribute_id - - -def add_service_attribute(service_id: int, attribute_id: int) -> bool: - try: - with get_db_connection() as conn: - with conn.cursor(row_factory=dict_row) as cursor: - cursor.execute( - 'SELECT 1 FROM "ServiceAttribute" WHERE "serviceId" = %s AND "attributeId" = %s', - (service_id, attribute_id), - ) - if cursor.fetchone(): - return True - cursor.execute( - 'INSERT INTO "ServiceAttribute" ("serviceId", "attributeId", "createdAt") VALUES (%s, %s, NOW())', - (service_id, attribute_id), - ) - conn.commit() - logger.info( - f"Added attribute id {attribute_id} to service {service_id}" - ) - return True - except Exception as e: - logger.error( - f"Error adding attribute id {attribute_id} to service {service_id}: {e}" - ) - return False - - -def remove_service_attribute(service_id: int, attribute_id: int) -> bool: - try: - with get_db_connection() as conn: - with conn.cursor(row_factory=dict_row) as cursor: - cursor.execute( - 'DELETE FROM "ServiceAttribute" WHERE "serviceId" = %s AND "attributeId" = %s', - (service_id, attribute_id), - ) - conn.commit() - logger.info( - f"Removed attribute id {attribute_id} from service {service_id}" - ) - return True - except Exception as e: - logger.error( - f"Error removing attribute id {attribute_id} from service {service_id}: {e}" - ) - return False - - -def add_service_attribute_by_slug(service_id: int, attribute_slug: str) -> bool: - attribute_id = get_attribute_id_by_slug(attribute_slug) - if attribute_id is None: - logger.error(f"Attribute with slug '{attribute_slug}' not found.") - return False - return add_service_attribute(service_id, attribute_id) - - -def remove_service_attribute_by_slug(service_id: int, attribute_slug: str) -> bool: - attribute_id = get_attribute_id_by_slug(attribute_slug) - if attribute_id is None: - logger.error(f"Attribute with slug '{attribute_slug}' not found.") - return False - return remove_service_attribute(service_id, attribute_id) - - -def save_tos_review(service_id: int, review: TosReviewType): - """ - Save a TOS review for a specific service. - - Args: - service_id: The ID of the service. - review: A TypedDict containing the review data. - """ - try: - # Serialize the dictionary to a JSON string for the database - review_json = json.dumps(review) - with get_db_connection() as conn: - with conn.cursor(row_factory=dict_row) as cursor: - cursor.execute( - """ - UPDATE "Service" - SET "tosReview" = %s, "tosReviewAt" = NOW() - WHERE id = %s - """, - (review_json, service_id), - ) - conn.commit() - logger.info(f"Successfully saved TOS review for service {service_id}") - except Exception as e: - logger.error(f"Error saving TOS review for service {service_id}: {e}") - - -def update_kyc_level(service_id: int, kyc_level: int) -> bool: - """ - Update the KYC level for a specific service. - - Args: - service_id: The ID of the service. - kyc_level: The new KYC level (0-4). - - Returns: - bool: True if the update was successful, False otherwise. - """ - try: - # Ensure the kyc_level is within the valid range - if not 0 <= kyc_level <= 4: - logger.error( - f"Invalid KYC level ({kyc_level}) for service {service_id}. Must be between 0 and 4." - ) - return False - - with get_db_connection() as conn: - with conn.cursor(row_factory=dict_row) as cursor: - cursor.execute( - """ - UPDATE "Service" - SET "kycLevel" = %s, "updatedAt" = NOW() - WHERE id = %s - """, - (kyc_level, service_id), - ) - conn.commit() - logger.info( - f"Successfully updated KYC level to {kyc_level} for service {service_id}" - ) - return True - except Exception as e: - logger.error(f"Error updating KYC level for service {service_id}: {e}") - return False - - -def get_comments(service_id: int, status: str = "APPROVED") -> List[Dict[str, Any]]: - """ - Get all comments for a specific service with the specified status. - - Args: - service_id: The ID of the service. - status: The status of comments to fetch (e.g. 'APPROVED', 'PENDING'). Defaults to 'APPROVED'. - - Returns: - A list of comment dictionaries. - NOTE: The structure returned by the SQL query might be different from CommentType. - Adjust CommentType or parsing if needed elsewhere. - """ - comments = [] - try: - with get_db_connection() as conn: - with conn.cursor(row_factory=dict_row) as cursor: - cursor.execute( - """ - WITH RECURSIVE comment_tree AS ( - -- Base case: get all root comments (no parent) - SELECT - c.id, - c.content, - c.rating, - c.upvotes, - c."createdAt", - c."updatedAt", - c."parentId", - c.status, - u.id as "authorId", - u.name as "authorName", - u."displayName" as "authorDisplayName", - u.picture as "authorPicture", - u.verified as "authorVerified", - 0 as depth - FROM "Comment" c - JOIN "User" u ON c."authorId" = u.id - WHERE c."serviceId" = %s - AND c.status = %s - AND c."parentId" IS NULL - - UNION ALL - - -- Recursive case: get all replies - SELECT - c.id, - c.content, - c.rating, - c.upvotes, - c."createdAt", - c."updatedAt", - c."parentId", - c.status, - u.id as "authorId", - u.name as "authorName", - u."displayName" as "authorDisplayName", - u.picture as "authorPicture", - u.verified as "authorVerified", - ct.depth + 1 - FROM "Comment" c - JOIN "User" u ON c."authorId" = u.id - JOIN comment_tree ct ON c."parentId" = ct.id - WHERE c.status = %s - ) - SELECT * FROM comment_tree - ORDER BY "createdAt" DESC, depth ASC - """, - (service_id, status, status), - ) - comments = cursor.fetchall() - except Exception as e: - logger.error( - f"Error fetching comments for service {service_id} with status {status}: {e}" - ) - - return comments - - -def get_max_comment_updated_at( - service_id: int, status: str = "APPROVED" -) -> Optional[datetime]: - """ - Get the maximum 'updatedAt' timestamp for comments of a specific service and status. - - Args: - service_id: The ID of the service. - status: The status of comments to consider. - - Returns: - The maximum 'updatedAt' timestamp as a datetime object, or None if no matching comments. - """ - max_updated_at = None - try: - with get_db_connection() as conn: - with ( - conn.cursor() as cursor - ): # dict_row not strictly needed for single value - cursor.execute( - """ - SELECT MAX("updatedAt") - FROM "Comment" - WHERE "serviceId" = %s AND status = %s - """, - (service_id, status), - ) - result = cursor.fetchone() - if result and result[0] is not None: - max_updated_at = result[0] - except Exception as e: - logger.error( - f"Error fetching max comment updatedAt for service {service_id} with status {status}: {e}" - ) - return max_updated_at - - -def save_user_sentiment( - service_id: int, - sentiment: Optional[CommentSentimentSummaryType], - last_processed_comment_timestamp: Optional[datetime], -): - """ - Save user sentiment for a specific service and the timestamp of the last comment processed. - - Args: - service_id: The ID of the service. - sentiment: A dictionary containing the sentiment data, or None to clear it. - last_processed_comment_timestamp: The 'updatedAt' timestamp of the most recent comment - considered in this sentiment analysis. Can be None. - """ - try: - sentiment_json = json.dumps(sentiment) if sentiment is not None else None - with get_db_connection() as conn: - with conn.cursor() as cursor: # row_factory not needed for UPDATE - cursor.execute( - """ - UPDATE "Service" - SET "userSentiment" = %s, "userSentimentAt" = %s - WHERE id = %s - """, - (sentiment_json, last_processed_comment_timestamp, service_id), - ) - conn.commit() - if sentiment: - logger.info( - f"Successfully saved user sentiment for service {service_id} with last comment processed at {last_processed_comment_timestamp}" - ) - else: - logger.info( - f"Successfully cleared user sentiment for service {service_id}, last comment processed at set to {last_processed_comment_timestamp}" - ) - except Exception as e: - logger.error(f"Error saving user sentiment for service {service_id}: {e}") - - -def update_comment_moderation(comment_data: CommentType): - """ - Update an existing comment in the database based on moderation results. - - Args: - comment_data: A TypedDict representing the comment data to update. - Expected keys are defined in CommentType. - """ - comment_id = comment_data.get("id") - if not comment_id: - logger.error("Cannot update comment: 'id' is missing from comment_data.") - return - - try: - with get_db_connection() as conn: - with conn.cursor() as cursor: - cursor.execute( - """ - UPDATE "Comment" - SET - status = %(status)s, - "requiresAdminReview" = %(requiresAdminReview)s, - "communityNote" = %(communityNote)s, - "internalNote" = %(internalNote)s, - "updatedAt" = NOW() - WHERE id = %(id)s - """, - comment_data, - ) - conn.commit() - logger.info(f"Successfully updated comment {comment_id}") - except Exception as e: - logger.error(f"Error updating comment {comment_id}: {e}") - - -def touch_service_updated_at(service_id: int) -> bool: - """ - Update the updatedAt field for a specific service to now. - - Args: - service_id: The ID of the service. - - Returns: - bool: True if the update was successful, False otherwise. - """ - try: - with get_db_connection() as conn: - with conn.cursor(row_factory=dict_row) as cursor: - cursor.execute( - """ - UPDATE "Service" - SET "updatedAt" = NOW() - WHERE id = %s - """, - (service_id,), - ) - conn.commit() - logger.info(f"Successfully touched updatedAt for service {service_id}") - return True - except Exception as e: - logger.error(f"Error touching updatedAt for service {service_id}: {e}") - return False - - -def run_db_query(query: Any, params: Optional[Any] = None) -> List[Dict[str, Any]]: - results = [] - try: - with get_db_connection() as conn: - with conn.cursor(row_factory=dict_row) as cursor: - if params is None: - cursor.execute(query) - else: - cursor.execute(query, params) - results = cursor.fetchall() - except Exception as e: - logger.error(f"Error running query: {e}") - return results - - -def execute_db_command(command: str, params: Optional[Any] = None) -> int: - """ - Execute a database command (INSERT, UPDATE, DELETE) and return affected rows. - - Args: - command: The SQL command string. - params: Optional parameters for the command. - - Returns: - The number of rows affected by the command. - """ - affected_rows = 0 - try: - with get_db_connection() as conn: - with conn.cursor() as cursor: - # Cast the string to the expected type to satisfy the type checker - # In runtime, this is equivalent to passing the command directly - cursor.execute(command, params) # type: ignore - affected_rows = cursor.rowcount - conn.commit() - logger.info(f"Executed command, {affected_rows} rows affected.") - except Exception as e: - logger.error(f"Error executing command: {e}") - return affected_rows - - -def create_attribute( - slug: str, - title: str, - description: str, - category: str, - type: str, - privacy_points: float = 0, - trust_points: float = 0, - overall_points: float = 0, -) -> Optional[int]: - """ - Create a new attribute in the database if it doesn't already exist. - - Args: - slug: The unique slug for the attribute. - title: The display title of the attribute. - description: The description of the attribute. - category: The category of the attribute (e.g., 'TRUST', 'PRIVACY'). - type: The type of the attribute (e.g., 'WARNING', 'FEATURE'). - privacy_points: Points affecting privacy score (default: 0). - trust_points: Points affecting trust score (default: 0). - overall_points: Points affecting overall score (default: 0). - - Returns: - The ID of the created (or existing) attribute, or None if creation failed. - """ - try: - with get_db_connection() as conn: - with conn.cursor(row_factory=dict_row) as cursor: - # First check if the attribute already exists - cursor.execute('SELECT id FROM "Attribute" WHERE slug = %s', (slug,)) - row = cursor.fetchone() - if row: - logger.info( - f"Attribute with slug '{slug}' already exists, id: {row['id']}" - ) - return row["id"] - - # Create the attribute if it doesn't exist - cursor.execute( - """ - INSERT INTO "Attribute" ( - slug, title, description, "privacyPoints", "trustPoints", - category, type, "createdAt", "updatedAt" - ) VALUES ( - %s, %s, %s, %s, %s, %s, %s, NOW(), NOW() - ) RETURNING id - """, - ( - slug, - title, - description, - privacy_points, - trust_points, - category, - type, - ), - ) - conn.commit() - result = cursor.fetchone() - if result is None: - logger.error( - f"Failed to retrieve ID for newly created attribute with slug '{slug}'" - ) - return None - attribute_id = result["id"] - logger.info( - f"Created new attribute with slug '{slug}', id: {attribute_id}" - ) - return attribute_id - except Exception as e: - logger.error(f"Error creating attribute with slug '{slug}': {e}") - return None diff --git a/pyworker/pyworker/scheduler.py b/pyworker/pyworker/scheduler.py deleted file mode 100644 index bd6164f..0000000 --- a/pyworker/pyworker/scheduler.py +++ /dev/null @@ -1,184 +0,0 @@ -""" -Scheduler module for managing task execution with cron. -""" - -import signal -import threading -from datetime import datetime -from types import FrameType -from typing import Any, Callable, Dict, List, ParamSpec, TypeVar - -from croniter import croniter - -from pyworker.database import close_db_pool -from .tasks import ( - CommentModerationTask, - ForceTriggersTask, - ServiceScoreRecalculationTask, - TosReviewTask, - UserSentimentTask, -) -from pyworker.utils.app_logging import setup_logging - -logger = setup_logging(__name__) - -P = ParamSpec("P") -R = TypeVar("R") - - -class TaskScheduler: - """Task scheduler for running tasks on a cron schedule.""" - - def __init__(self): - """Initialize the task scheduler.""" - self.tasks: Dict[str, Dict[str, Any]] = {} - self.running = False - self.threads: List[threading.Thread] = [] - self.stop_event = threading.Event() - self.logger = logger - - # Set up signal handlers - signal.signal(signal.SIGINT, self._handle_signal) - signal.signal(signal.SIGTERM, self._handle_signal) - - def _handle_signal(self, signum: int, frame: FrameType | None) -> None: - """Handle termination signals.""" - self.logger.info(f"Received signal {signum}, shutting down...") - self.stop() - - def register_task( - self, - task_name: str, - cron_expression: str, - task_func: Callable[P, R], - *args: P.args, - **kwargs: P.kwargs, - ) -> None: - """ - Register a task to be scheduled. - - Args: - task_name: Name of the task. - cron_expression: Cron expression defining the schedule. - task_func: Function to execute. - *args: Arguments to pass to the task function. - **kwargs: Keyword arguments to pass to the task function. - """ - # Declare task_instance variable with type annotation upfront - task_instance: Any = None - - # Initialize the appropriate task class based on the task name - if task_name.lower() == "tosreview": - task_instance = TosReviewTask() - elif task_name.lower() == "user_sentiment": - task_instance = UserSentimentTask() - elif task_name.lower() == "comment_moderation": - task_instance = CommentModerationTask() - elif task_name.lower() == "force_triggers": - task_instance = ForceTriggersTask() - elif task_name.lower() == "service_score_recalc": - task_instance = ServiceScoreRecalculationTask() - else: - self.logger.warning(f"Unknown task '{task_name}', skipping") - return - - self.tasks[task_name] = { - "cron": cron_expression, - "func": task_func, - "instance": task_instance, - "args": args, - "kwargs": kwargs, - } - self.logger.info( - f"Registered task '{task_name}' with schedule: {cron_expression}" - ) - - def _run_task(self, task_name: str, task_info: Dict[str, Any]): - """ - Run a task on its schedule. - - Args: - task_name: Name of the task. - task_info: Task information including function and schedule. - """ - self.logger.info(f"Starting scheduler for task '{task_name}'") - - # Parse the cron expression - cron = croniter(task_info["cron"], datetime.now()) - - while not self.stop_event.is_set(): - # Get the next run time - next_run = cron.get_next(datetime) - self.logger.info(f"Next run for task '{task_name}': {next_run}") - - # Sleep until the next run time - now = datetime.now() - sleep_seconds = (next_run - now).total_seconds() - - if sleep_seconds > 0: - # Wait until next run time or until stop event is set - if self.stop_event.wait(sleep_seconds): - break - - # Run the task if we haven't been stopped - if not self.stop_event.is_set(): - try: - self.logger.info(f"Running task '{task_name}'") - # Use task instance as a context manager to ensure - # a single database connection is used for the entire task - with task_info["instance"] as task_instance: - # Execute the task instance's run method directly - task_instance.run() - self.logger.info(f"Task '{task_name}' completed") - except Exception as e: - self.logger.exception(f"Error running task '{task_name}': {e}") - finally: - # Close the database pool after task execution - close_db_pool() - - def start(self): - """Start the scheduler.""" - if self.running: - self.logger.warning("Scheduler is already running") - return - - self.logger.info("Starting scheduler") - self.running = True - self.stop_event.clear() - - # Start a thread for each task - for task_name, task_info in self.tasks.items(): - thread = threading.Thread( - target=self._run_task, - args=(task_name, task_info), - name=f"scheduler-{task_name}", - ) - thread.daemon = True - thread.start() - self.threads.append(thread) - - self.logger.info(f"Started {len(self.threads)} scheduler threads") - - def stop(self): - """Stop the scheduler.""" - if not self.running: - return - - self.logger.info("Stopping scheduler") - self.running = False - self.stop_event.set() - - # Wait for all threads to terminate - for thread in self.threads: - thread.join(timeout=5.0) - - self.threads = [] - - # Close database pool when the scheduler stops - close_db_pool() - - self.logger.info("Scheduler stopped") - - def is_running(self) -> bool: - """Check if the scheduler is running.""" - return self.running diff --git a/pyworker/pyworker/tasks/__init__.py b/pyworker/pyworker/tasks/__init__.py deleted file mode 100644 index 4c1143a..0000000 --- a/pyworker/pyworker/tasks/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Task modules for the pyworker package.""" - -from .base import Task -from .comment_moderation import CommentModerationTask -from .force_triggers import ForceTriggersTask -from .service_score_recalc import ServiceScoreRecalculationTask -from .tos_review import TosReviewTask -from .user_sentiment import UserSentimentTask - -__all__ = [ - "Task", - "CommentModerationTask", - "ForceTriggersTask", - "ServiceScoreRecalculationTask", - "TosReviewTask", - "UserSentimentTask", -] diff --git a/pyworker/pyworker/tasks/base.py b/pyworker/pyworker/tasks/base.py deleted file mode 100644 index 6d1d260..0000000 --- a/pyworker/pyworker/tasks/base.py +++ /dev/null @@ -1,64 +0,0 @@ -""" -Base task module for the pyworker package. -""" - -from abc import ABC, abstractmethod -from contextlib import AbstractContextManager -from typing import Any, Optional, Type - -from pyworker.database import get_db_connection -from pyworker.utils.app_logging import setup_logging - -logger = setup_logging(__name__) - - -class Task(ABC): - """Base class for all worker tasks.""" - - def __init__(self, name: str): - """ - Initialize a task. - - Args: - name: The name of the task. - """ - self.name = name - self.logger = setup_logging(f"pyworker.task.{name}") - self.conn: Optional[Any] = None - self._context: Optional[AbstractContextManager[Any]] = None - - def __enter__(self): - """Enter context manager, acquiring a database connection.""" - self._context = get_db_connection() - self.conn = self._context.__enter__() - return self - - def __exit__( - self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[Any], - ) -> Optional[bool]: - """Exit context manager, releasing the database connection.""" - if self._context: - return self._context.__exit__(exc_type, exc_val, exc_tb) - return None - - @abstractmethod - def run(self, *args: Any, **kwargs: Any) -> Any: - """ - Run the task. - - This method must be implemented by subclasses. - - Args: - *args: Variable length argument list. - **kwargs: Arbitrary keyword arguments. - - Returns: - The result of the task. - """ - pass - - def __str__(self) -> str: - return f"{self.__class__.__name__}(name={self.name})" diff --git a/pyworker/pyworker/tasks/comment_moderation.py b/pyworker/pyworker/tasks/comment_moderation.py deleted file mode 100644 index 5833ec1..0000000 --- a/pyworker/pyworker/tasks/comment_moderation.py +++ /dev/null @@ -1,112 +0,0 @@ -""" -Task for summarizing comments and getting overal sentiment -""" - -import json -from datetime import datetime -from typing import Any, Dict, List - -# Import types from database.py -from pyworker.database import ( # type: ignore - CommentType, - get_comments, - update_comment_moderation, -) -from pyworker.tasks.base import Task # type: ignore -from pyworker.utils.ai import prompt_comment_moderation - - -class DateTimeEncoder(json.JSONEncoder): - def default(self, o: Any) -> Any: - if isinstance(o, datetime): - return o.isoformat() - return super().default(o) - - -class CommentModerationTask(Task): - """Task for summarizing comments and getting overal sentiment""" - - def __init__(self): - """Initialize the comment moderation task.""" - super().__init__("comment_moderation") - - def run(self, service: Dict[str, Any]) -> bool: - """ - Run the comment moderation task. - Returns True if comments were processed, False otherwise. - """ - service_id = service["id"] - service_name = service["name"] - - # Query the approved comments for the service - # get_comments is type ignored, so we assume it returns List[Dict[str, Any]] - comments: List[Dict[str, Any]] = get_comments(service_id, status="PENDING") - - if not comments: - self.logger.info( - f"No pending comments found for service {service_name} (ID: {service_id}) during task run." - ) - return False - - self.logger.info( - f"Found {len(comments)} pending comments for service {service_name} (ID: {service_id}). Starting processing." - ) - - processed_at_least_one = False - for comment_data in comments: - # Assert the type for the individual dictionary for type checking within the loop - comment: CommentType = comment_data # type: ignore - - # Query OpenAI to get the sentiment summary - moderation = prompt_comment_moderation( - f"Information about the service: {service}\\nCurrent time: {datetime.now()}\\n\\nComment to moderate: {json.dumps(comment, cls=DateTimeEncoder)}" - ) - - modstring = f"Comment {comment['id']} " - - if moderation["isSpam"] and moderation["commentQuality"] > 5: - comment["status"] = "HUMAN_PENDING" - modstring += " marked as HUMAN_PENDING" - elif moderation["isSpam"] and moderation["commentQuality"] <= 5: - comment["status"] = "REJECTED" - modstring += " marked as REJECTED" - - if moderation["requiresAdminReview"]: - comment["requiresAdminReview"] = True - modstring += " requires admin review" - # Ensure status is HUMAN_PENDING if admin review is required, unless already REJECTED - if comment.get("status") != "REJECTED": - comment["status"] = "HUMAN_PENDING" - if ( - "marked as HUMAN_PENDING" not in modstring - ): # Avoid duplicate message - modstring += " marked as HUMAN_PENDING" - else: - comment["requiresAdminReview"] = False - if ( - comment.get("status") != "HUMAN_PENDING" - and comment.get("status") != "REJECTED" - ): - comment["status"] = "APPROVED" - modstring += " marked as APPROVED" - - if moderation.get("moderationNote"): # Check if key exists - comment["communityNote"] = moderation["contextNote"] - modstring += " with moderation note: " + moderation["contextNote"] - else: - comment["communityNote"] = None - - if moderation.get("internalNote"): # Check if key exists - comment["internalNote"] = moderation["internalNote"] - modstring += ( - " with internal note: " + moderation["internalNote"] - ) # Changed from spam reason for clarity - else: - comment["internalNote"] = None - - # Save the sentiment summary to the database - self.logger.info(f"{modstring}") - update_comment_moderation(comment) - processed_at_least_one = True - - return processed_at_least_one diff --git a/pyworker/pyworker/tasks/force_triggers.py b/pyworker/pyworker/tasks/force_triggers.py deleted file mode 100644 index 60dfefd..0000000 --- a/pyworker/pyworker/tasks/force_triggers.py +++ /dev/null @@ -1,43 +0,0 @@ -from pyworker.tasks.base import Task -from pyworker.utils.app_logging import setup_logging - -logger = setup_logging(__name__) - - -class ForceTriggersTask(Task): - """ - Force triggers to run under certain conditions. - """ - - RECENT_LISTED_INTERVAL_DAYS = 15 - - def __init__(self): - super().__init__("force_triggers") - - def run(self) -> bool: - logger.info(f"Starting {self.name} task.") - - # Use the connection provided by the base Task class - if not self.conn: - logger.error("No database connection available") - return False - - update_query = f""" - UPDATE "Service" - SET "isRecentlyListed" = FALSE, "updatedAt" = NOW() - WHERE "isRecentlyListed" = TRUE - AND "listedAt" IS NOT NULL - AND "listedAt" < NOW() - INTERVAL '{self.RECENT_LISTED_INTERVAL_DAYS} days' - """ - try: - with self.conn.cursor() as cursor: - cursor.execute(update_query) - self.conn.commit() - added_count = cursor.rowcount - logger.info(f"Updated {added_count} services.") - except Exception as e: - logger.error(f"Error updating services: {e}") - return False - - logger.info(f"{self.name} task completed successfully.") - return True diff --git a/pyworker/pyworker/tasks/service_score_recalc.py b/pyworker/pyworker/tasks/service_score_recalc.py deleted file mode 100644 index abb8304..0000000 --- a/pyworker/pyworker/tasks/service_score_recalc.py +++ /dev/null @@ -1,325 +0,0 @@ -""" -Task to recalculate service scores based on attribute changes. -""" - -from typing import Optional - -from pyworker.tasks.base import Task -from pyworker.utils.app_logging import setup_logging - -logger = setup_logging(__name__) - - -class ServiceScoreRecalculationTask(Task): - """ - Process pending service score recalculation jobs. - - This task fetches jobs from the ServiceScoreRecalculationJob table - and recalculates service scores using the PostgreSQL functions. - """ - - def __init__(self): - super().__init__("service_score_recalc") - - def run(self, service_id: Optional[int] = None) -> bool: - """ - Process score recalculation jobs from the ServiceScoreRecalculationJob table. - - Args: - service_id: Optional service ID to process only that specific service - - Returns: - bool: True if successful, False otherwise - """ - logger.info(f"Starting {self.name} task.") - processed_count = 0 - error_count = 0 - batch_size = 50 - - # Use the connection provided by the base Task class - if not self.conn: - logger.error("No database connection available") - return False - - try: - # Build query - either for a specific service or all pending jobs - if service_id: - select_query = """ - SELECT id, "serviceId" - FROM "ServiceScoreRecalculationJob" - WHERE "serviceId" = %s AND "processedAt" IS NULL - ORDER BY "createdAt" ASC - """ - params = [service_id] - else: - select_query = """ - SELECT id, "serviceId" - FROM "ServiceScoreRecalculationJob" - WHERE "processedAt" IS NULL - ORDER BY "createdAt" ASC - LIMIT %s - """ - params = [batch_size] - - # Fetch jobs - with self.conn.cursor() as cursor: - cursor.execute(select_query, params) - unprocessed_jobs = cursor.fetchall() - - if not unprocessed_jobs: - logger.info("No pending service score recalculation jobs found.") - return True - - logger.info( - f"Processing {len(unprocessed_jobs)} service score recalculation jobs." - ) - - # Process each job - for job in unprocessed_jobs: - job_id = job[0] # First column is id - svc_id = job[1] # Second column is serviceId - - try: - self._process_service_score(svc_id, job_id) - processed_count += 1 - logger.debug( - f"Successfully processed job {job_id} for service {svc_id}" - ) - except Exception as e: - if self.conn: - self.conn.rollback() - error_count += 1 - logger.error( - f"Error processing job {job_id} for service {svc_id}: {str(e)}", - exc_info=True, - ) - - logger.info( - f"{self.name} task completed. Processed: {processed_count}, Errors: {error_count}" - ) - return processed_count > 0 or error_count == 0 - - except Exception as e: - if self.conn: - self.conn.rollback() - logger.error(f"Failed to run {self.name} task: {str(e)}", exc_info=True) - return False - - def _process_service_score(self, service_id: int, job_id: int) -> None: - """ - Process a single service score recalculation job. - - Args: - service_id: The service ID to recalculate scores for - job_id: The job ID to mark as processed - """ - if not self.conn: - raise ValueError("No database connection available") - - with self.conn.cursor() as cursor: - # 1. Calculate privacy score - cursor.execute("SELECT calculate_privacy_score(%s)", [service_id]) - privacy_score = cursor.fetchone()[0] - - # 2. Calculate trust score - cursor.execute("SELECT calculate_trust_score(%s)", [service_id]) - trust_score = cursor.fetchone()[0] - - # 3. Calculate overall score - cursor.execute( - "SELECT calculate_overall_score(%s, %s, %s)", - [service_id, privacy_score, trust_score], - ) - overall_score = cursor.fetchone()[0] - - # 4. Check for verification status and cap score if needed - cursor.execute( - 'SELECT "verificationStatus" FROM "Service" WHERE id = %s', - [service_id], - ) - result = cursor.fetchone() - if result is None: - logger.warning( - f"Service with ID {service_id} not found. Deleting job {job_id}." - ) - # Delete the job if the service is gone - cursor.execute( - """ - DELETE FROM "ServiceScoreRecalculationJob" - WHERE id = %s - """, - [job_id], - ) - self.conn.commit() - return # Skip the rest of the processing for this job - - status = result[0] - - if status == "VERIFICATION_FAILED": - if overall_score > 3: - overall_score = 3 - elif overall_score < 0: - overall_score = 0 - - # 5. Update the service with recalculated scores - cursor.execute( - """ - UPDATE "Service" - SET "privacyScore" = %s, "trustScore" = %s, "overallScore" = %s - WHERE id = %s - """, - [privacy_score, trust_score, overall_score, service_id], - ) - - # 6. Mark the job as processed - cursor.execute( - """ - UPDATE "ServiceScoreRecalculationJob" - SET "processedAt" = NOW() - WHERE id = %s - """, - [job_id], - ) - - # Commit the transaction - if self.conn: - self.conn.commit() - - def recalculate_all_services(self) -> bool: - """ - Recalculate scores for all active services. - Useful for batch updates after attribute changes. - - Returns: - bool: True if successful, False otherwise - """ - logger.info("Starting recalculation for all active services.") - - if not self.conn: - logger.error("No database connection available") - return False - - try: - # Get all active service IDs - with self.conn.cursor() as cursor: - cursor.execute( - """ - SELECT id - FROM "Service" - WHERE "isActive" = TRUE - """ - ) - services = cursor.fetchall() - - if not services: - logger.info("No active services found.") - return True - - logger.info(f"Found {len(services)} active services to recalculate.") - - # Queue recalculation jobs for all services - inserted_count = 0 - for service in services: - service_id = service[0] - try: - if self.conn: - with self.conn.cursor() as cursor: - cursor.execute( - """ - INSERT INTO "ServiceScoreRecalculationJob" ("serviceId", "createdAt", "processedAt") - VALUES (%s, NOW(), NULL) - ON CONFLICT ("serviceId") DO UPDATE - SET "processedAt" = NULL, "createdAt" = NOW() - """, - [service_id], - ) - self.conn.commit() - inserted_count += 1 - except Exception as e: - if self.conn: - self.conn.rollback() - logger.error( - f"Error queueing job for service {service_id}: {str(e)}" - ) - - logger.info(f"Successfully queued {inserted_count} recalculation jobs.") - return True - - except Exception as e: - if self.conn: - self.conn.rollback() - logger.error(f"Failed to queue recalculation jobs: {str(e)}", exc_info=True) - return False - - def recalculate_for_attribute(self, attribute_id: int) -> bool: - """ - Recalculate scores for all services associated with a specific attribute. - - Args: - attribute_id: The attribute ID to recalculate scores for - - Returns: - bool: True if successful, False otherwise - """ - logger.info( - f"Starting recalculation for services with attribute ID {attribute_id}." - ) - - if not self.conn: - logger.error("No database connection available") - return False - - try: - # Get all services associated with this attribute - with self.conn.cursor() as cursor: - cursor.execute( - """ - SELECT DISTINCT sa."serviceId" - FROM "ServiceAttribute" sa - WHERE sa."attributeId" = %s - """, - [attribute_id], - ) - services = cursor.fetchall() - - if not services: - logger.info(f"No services found with attribute ID {attribute_id}.") - return True - - logger.info( - f"Found {len(services)} services with attribute ID {attribute_id}." - ) - - # Queue recalculation jobs for all services with this attribute - inserted_count = 0 - for service in services: - service_id = service[0] - try: - if self.conn: - with self.conn.cursor() as cursor: - cursor.execute( - """ - INSERT INTO "ServiceScoreRecalculationJob" ("serviceId", "createdAt", "processedAt") - VALUES (%s, NOW(), NULL) - ON CONFLICT ("serviceId") DO UPDATE - SET "processedAt" = NULL, "createdAt" = NOW() - """, - [service_id], - ) - self.conn.commit() - inserted_count += 1 - except Exception as e: - if self.conn: - self.conn.rollback() - logger.error( - f"Error queueing job for service {service_id}: {str(e)}" - ) - - logger.info(f"Successfully queued {inserted_count} recalculation jobs.") - return True - - except Exception as e: - if self.conn: - self.conn.rollback() - logger.error(f"Failed to queue recalculation jobs: {str(e)}", exc_info=True) - return False diff --git a/pyworker/pyworker/tasks/tos_review.py b/pyworker/pyworker/tasks/tos_review.py deleted file mode 100644 index c5d8da9..0000000 --- a/pyworker/pyworker/tasks/tos_review.py +++ /dev/null @@ -1,116 +0,0 @@ -""" -Task for retrieving Terms of Service (TOS) text. -""" - -import hashlib -from typing import Any, Dict, Optional - -from pyworker.database import TosReviewType, save_tos_review, update_kyc_level -from pyworker.tasks.base import Task -from pyworker.utils.ai import prompt_check_tos_review, prompt_tos_review -from pyworker.utils.crawl import fetch_markdown - - -class TosReviewTask(Task): - """Task for retrieving Terms of Service (TOS) text.""" - - def __init__(self): - """Initialize the TOS review task.""" - super().__init__("tos_review") - - def run(self, service: Dict[str, Any]) -> Optional[TosReviewType]: - """ - Review TOS text for a service. - - Args: - service: A dictionary containing service information. - - Returns: - A dictionary mapping TOS URLs to their retrieved text, or None if no TOS URLs. - """ - service_id = service["id"] - service_name = service["name"] - verification_status = service.get("verificationStatus") - - # Only process verified or approved services - if verification_status not in ["VERIFICATION_SUCCESS", "APPROVED"]: - self.logger.info( - f"Skipping TOS review for service: {service_name} (ID: {service_id}) - Status: {verification_status}" - ) - return None - - tos_urls = service.get("tosUrls", []) - - if not tos_urls: - self.logger.info( - f"No TOS URLs found for service: {service_name} (ID: {service_id})" - ) - return None - - self.logger.info( - f"Reviewing TOS for service: {service_name} (ID: {service_id})" - ) - self.logger.info(f"TOS URLs: {tos_urls}") - - for tos_url in tos_urls: - api_url = f"{tos_url}" - self.logger.info(f"Fetching TOS from URL: {api_url}") - - # Sleep for 1 second to avoid rate limiting - content = fetch_markdown(api_url) - - if content: - # Hash the content to avoid repeating the same content - content_hash = hashlib.sha256(content.encode()).hexdigest() - self.logger.info(f"Content hash: {content_hash}") - - # service.get("tosReview") can be None if the DB field is NULL. - # Default to an empty dict to prevent AttributeError on .get() - tos_review_data_from_service: Optional[Dict[str, Any]] = service.get( - "tosReview" - ) - tos_review: Dict[str, Any] = ( - tos_review_data_from_service - if tos_review_data_from_service is not None - else {} - ) - - stored_hash = tos_review.get("contentHash") - - # Skip processing if we've seen this content before - if stored_hash == content_hash: - self.logger.info( - f"Skipping already processed TOS content with hash: {content_hash}" - ) - continue - - # Skip incomplete TOS content - check = prompt_check_tos_review(content) - if not check: - continue - elif not check["isComplete"]: - continue - - # Query OpenAI to summarize the content - review = prompt_tos_review(content) - - if review: - review["contentHash"] = content_hash - # Save the review to the database - save_tos_review(service_id, review) - - # Update the KYC level based on the review - if "kycLevel" in review: - kyc_level = review["kycLevel"] - self.logger.info( - f"Updating KYC level to {kyc_level} for service {service_name}" - ) - update_kyc_level(service_id, kyc_level) - # no need to check other TOS URLs - break - - return review - else: - self.logger.warning( - f"Failed to retrieve TOS content for URL: {tos_url}" - ) diff --git a/pyworker/pyworker/tasks/user_sentiment.py b/pyworker/pyworker/tasks/user_sentiment.py deleted file mode 100644 index 6e20ee6..0000000 --- a/pyworker/pyworker/tasks/user_sentiment.py +++ /dev/null @@ -1,134 +0,0 @@ -""" -Task for summarizing comments and getting overal sentiment -""" - -import json -from datetime import datetime -from typing import Any, Dict, Optional - -from pyworker.database import ( - CommentSentimentSummaryType, - get_comments, - get_max_comment_updated_at, - save_user_sentiment, -) -from pyworker.tasks.base import Task -from pyworker.utils.ai import ( - prompt_comment_sentiment_summary, -) - - -class DateTimeEncoder(json.JSONEncoder): - def default(self, o: Any) -> Any: - if isinstance(o, datetime): - return o.isoformat() - return super().default(o) - - -class UserSentimentTask(Task): - """Task for summarizing comments and getting overal sentiment""" - - def __init__(self): - """Initialize the comment sentiment summary task.""" - super().__init__("comment_sentiment_summary") - - def run(self, service: Dict[str, Any]) -> Optional[CommentSentimentSummaryType]: - """ - Run the comment sentiment summary task. - Skips execution if no new comments are found since the last run. - Clears sentiment if all comments are removed. - """ - service_id = service["id"] - service_name = service["name"] - current_user_sentiment_at: Optional[datetime] = service.get("userSentimentAt") - - if isinstance(current_user_sentiment_at, str): - try: - current_user_sentiment_at = datetime.fromisoformat( - str(current_user_sentiment_at).replace("Z", "+00:00") - ) - except ValueError: - self.logger.warning( - f"Could not parse userSentimentAt string '{current_user_sentiment_at}' for service {service_id}. Treating as None." - ) - current_user_sentiment_at = None - - # Get the timestamp of the most recent approved comment - max_comment_updated_at = get_max_comment_updated_at( - service_id, status="APPROVED" - ) - - self.logger.info( - f"Service {service_name} (ID: {service_id}): Current userSentimentAt: {current_user_sentiment_at}, Max approved comment updatedAt: {max_comment_updated_at}" - ) - - if max_comment_updated_at is None: - self.logger.info( - f"No approved comments found for service {service_name} (ID: {service_id})." - ) - # If there was a sentiment before and now no comments, clear it. - if service.get("userSentiment") is not None: - self.logger.info( - f"Clearing existing sentiment for service {service_name} (ID: {service_id}) as no approved comments are present." - ) - save_user_sentiment(service_id, None, None) - return None - - if ( - current_user_sentiment_at is not None - and max_comment_updated_at <= current_user_sentiment_at - ): - self.logger.info( - f"No new approved comments for service {service_name} (ID: {service_id}) since last sentiment analysis ({current_user_sentiment_at}). Skipping." - ) - # Optionally, return the existing sentiment if needed: - # existing_sentiment = service.get("userSentiment") - # return existing_sentiment if isinstance(existing_sentiment, dict) else None - return None - - # Query the approved comments for the service - # get_comments defaults to status="APPROVED" - comments = get_comments(service_id) - - self.logger.info( - f"Found {len(comments)} comments for service {service_name} (ID: {service_id}) to process." - ) - - if not comments: - # This case could occur if max_comment_updated_at found a comment, - # but get_comments filters it out or it was deleted just before get_comments ran. - self.logger.info( - f"No comments to process for service {service_name} (ID: {service_id}) after fetching (e.g. due to filtering or deletion)." - ) - if service.get("userSentiment") is not None: - self.logger.info( - f"Clearing existing sentiment for service {service_name} (ID: {service_id}) as no processable comments found." - ) - # Use max_comment_updated_at as the reference point for when this check was made. - save_user_sentiment(service_id, None, max_comment_updated_at) - return None - - # Query OpenAI to get the sentiment summary - try: - sentiment_summary = prompt_comment_sentiment_summary( - json.dumps(comments, cls=DateTimeEncoder) - ) - except Exception as e: - self.logger.error( - f"Failed to generate sentiment summary for service {service_name} (ID: {service_id}): {e}" - ) - return None - - if not sentiment_summary: # Defensive check if prompt could return None/empty - self.logger.warning( - f"Sentiment summary generation returned empty for service {service_name} (ID: {service_id})." - ) - return None - - # Save the sentiment summary to the database, using max_comment_updated_at - save_user_sentiment(service_id, sentiment_summary, max_comment_updated_at) - self.logger.info( - f"Successfully processed and saved user sentiment for service {service_name} (ID: {service_id})." - ) - - return sentiment_summary diff --git a/pyworker/pyworker/utils/__init__.py b/pyworker/pyworker/utils/__init__.py deleted file mode 100644 index d381a9c..0000000 --- a/pyworker/pyworker/utils/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Utility modules for the pyworker package.""" \ No newline at end of file diff --git a/pyworker/pyworker/utils/ai.py b/pyworker/pyworker/utils/ai.py deleted file mode 100644 index b7025df..0000000 --- a/pyworker/pyworker/utils/ai.py +++ /dev/null @@ -1,261 +0,0 @@ -import os -import time -from typing import Any, Dict, List, Literal, TypedDict, cast - -from json_repair import repair_json -from openai import OpenAI, OpenAIError -from openai.types.chat import ChatCompletionMessageParam - -from pyworker.database import ( - CommentModerationType, - CommentSentimentSummaryType, - TosReviewType, -) -from pyworker.utils.app_logging import setup_logging - -logger = setup_logging(__name__) - - -client = OpenAI( - base_url=os.environ.get("OPENAI_BASE_URL"), - api_key=os.environ.get("OPENAI_API_KEY"), -) - - -def query_openai_json( - messages: List[ChatCompletionMessageParam], - model: str = os.environ.get("OPENAI_MODEL", "deepseek-chat-cheaper"), -) -> Dict[str, Any]: - max_retries = int(os.environ.get("OPENAI_RETRY", 3)) - retry_delay = 30 - last_error = None - - for attempt in range(max_retries): - try: - completion = client.chat.completions.create( - model=model, - messages=messages, - ) - content = completion.choices[0].message.content - if content is None: - raise ValueError("OpenAI response content is None") - - logger.debug(f"Raw AI response content: {content}") - - try: - result = repair_json(content) - - if isinstance(result, str): - import json - - result = json.loads(result) - - if not isinstance(result, dict): - logger.error( - f"Repaired JSON is not a dictionary. Type: {type(result)}, Value: {result}" - ) - raise TypeError( - f"Expected a dictionary from AI response, but got {type(result)}" - ) - - return result - except Exception as e: - logger.error(f"Failed to process JSON response: {e}") - logger.error(f"Raw content was: {content}") - raise - - except (OpenAIError, ValueError, TypeError) as e: - last_error = e - if attempt == max_retries - 1: # Last attempt - logger.error(f"Failed after {max_retries} attempts. Last error: {e}") - raise last_error - logger.warning( - f"Attempt {attempt + 1} failed: {e}. Retrying in {retry_delay} seconds..." - ) - time.sleep(retry_delay) - retry_delay *= 2 # Exponential backoff - - # This line should never be reached due to the raise in the last attempt - raise last_error # type: ignore - - -ReasonType = Literal["js_required", "firewalled", "other"] - - -class TosReviewCheck(TypedDict): - isComplete: bool - - -def prompt_check_tos_review(content: str) -> TosReviewCheck: - messages: List[ChatCompletionMessageParam] = [ - {"role": "system", "content": PROMPT_CHECK_TOS_REVIEW}, - {"role": "user", "content": content}, - ] - - result_dict = query_openai_json(messages, model="openai/gpt-4.1-mini") - - return cast(TosReviewCheck, result_dict) - - -def prompt_tos_review(content: str) -> TosReviewType: - messages: List[ChatCompletionMessageParam] = [ - {"role": "system", "content": PROMPT_TOS_REVIEW}, - {"role": "user", "content": content}, - ] - - result_dict = query_openai_json(messages) - - return cast(TosReviewType, result_dict) - - -def prompt_comment_sentiment_summary(content: str) -> CommentSentimentSummaryType: - messages: List[ChatCompletionMessageParam] = [ - {"role": "system", "content": PROMPT_COMMENT_SENTIMENT_SUMMARY}, - {"role": "user", "content": content}, - ] - - result_dict = query_openai_json(messages) - return cast(CommentSentimentSummaryType, result_dict) - - -def prompt_comment_moderation(content: str) -> CommentModerationType: - messages: List[ChatCompletionMessageParam] = [ - {"role": "system", "content": PROMPT_COMMENT_MODERATION}, - {"role": "user", "content": content}, - ] - - result_dict = query_openai_json(messages) - - return cast(CommentModerationType, result_dict) - - -PROMPT_CHECK_TOS_REVIEW = """ -You will receive the Markdown content of a website page. Determine if the page is a complete. If the page was blocked (e.g. by Cloudflare or similar), incomplete (e.g. requires JavaScript), irrelevant (login/signup/CAPTCHA), set isComplete to false. - -If the page contains meaningful, coherent, valid service information or policy content, with no obvious blocking or truncation, set isComplete to true. - -Return only this JSON and nothing else: - -{"isComplete": true} or {"isComplete": false} -""" - -PROMPT_TOS_REVIEW = """ -You are a privacy analysis AI tasked with reviewing Terms of Service documents. -Your goal is to identify key information about data collection, privacy implications, and user rights. -You are a privacy advocate and you are looking for the most important information for the user in regards to privacy, kyc, self-sovereignity, anonymity, etc. -Analyze the provided Terms of Service and extract the following information: - -1. KYC level is on a scale of 1 to 4: - - **Guaranteed no KYC (Level 0)**: Terms explicitly state KYC will never be requested. - - **No KYC mention (Level 1)**: No mention of current or future KYC requirements. The document does not mention KYC at all. - - **KYC on authorities request (Level 2)**: No routine KYC, but may share data, block funds or reject transactions. Cooperates with authorities. - - **Shotgun KYC (Level 3)**: May request KYC and block funds based on automated transaction flagging system. It is not mandatory by default, but can be requested at any time, for any reason. - - **Mandatory KYC (Level 4)**: Required for key features or for user registration. -2. Overall summary of the terms of service, must be concise and to the point, no more than 250 characters. Use markdown formatting to highlight the most important information. Plain english. -3. Complexity of the terms of service text for a non-technical user, must be a string of 'low', 'medium', 'high'. -4. 'highlights': The important bits of information from the ToS document for the user to know. Always related to privacy, kyc, self-sovereignity, anonymity, custody, censorship resistance, etc. No need to mention these topics, just the important bits of information from the ToS document. - - important things to look for: automated transaction scanning, rejection or block of funds, refund policy (does it require KYC?), data sharing, logging, kyc requirements, etc. - - if No reference to KYC or proof of funds checks is mentioned or required, you don't need to mention it in the highlights, it is already implied from the kycLevel. - - Try to avoid obvious statements that can be infered from other, more important, highlights. Keep it short and concise only with the most important information for the user. - - You must strictly adhere to the document information, do not make up or infer information, do not make assumptions, do not add any information that is not explicitly stated in the document. -Format your response as a valid JSON object with the following structure: - -type TosReview = { - kycLevel: 0 | 1 | 2 | 3 | 4 - /** Less than 200 characters */ - summary: MarkdownString - complexity: 'high' | 'low' | 'medium' - highlights: { - /** Very short title, max 2-3 words */ - title: string - /** Less than 200 characters. Highlight the most important information with markdown formatting. */ - content: MarkdownString - /** In regards to KYC, Privacy, Anonymity, Self-Sovereignity, etc. */ - /** anything that could harm the user's privacy, identity, self-sovereignity or anonymity is negative, anything that otherwise helps is positive. else it is neutral. */ - rating: 'negative' | 'neutral' | 'positive' - }[] -} - -The rating is a number between 0 and 2, where 0 is informative, 1 is warning, and 2 is critical. - -Be concise but thorough, and make sure your output is properly formatted JSON. -""" - -PROMPT_COMMENT_SENTIMENT_SUMMARY = """ -You will be given a list of user comments to a service. -Your task is to summarize the comments in a way that is easy to understand and to the point. -The summary should be concise and to the point, no more than 150 words. -Use markdown formatting to highlight in bold the most important information. Only bold is allowed. - -You must format your response as a valid JSON object with the following structure: - -interface CommentSummary { - summary: string; - sentiment: 'positive'|'negative'|'neutral'; - whatUsersLike: string[]; // Concise, 2-3 words, max 4 - whatUsersDislike: string[]; // Concise, 2-3 words, max 4 -} - -Always avoid repeating information in the list of what users like or dislike. Also, make sure you keep the summary short and concise, no more than 150 words. Ignore irrelevant comments. Make an item for each like/dislike, avoid something like 'No logs / Audited', it should be 'No logs' and 'Audited' as separate items. - -You must return a valid raw JSON object, without any other text or formatting. -""" - -PROMPT_COMMENT_MODERATION = """ -You are kycnot.me’s comment moderation API. Your sole responsibility is to analyze user comments on directory listings (cryptocurrency, anonymity, privacy services) and decide, in strict accordance with the schema and rules below, whether each comment is spam, needs admin review, and its overall quality for our platform. Output ONLY a plain, valid JSON object, with NO markdown, extra text, annotations, or code blocks. - -## Output Schema - -interface CommentModeration { - isSpam: boolean; - requiresAdminReview: boolean; - contextNote: string; - internalNote: string; - commentQuality: 0|1|2|3|4|5|6|7|8|9|10; -} - -## FIELD EXPLANATION - -- isSpam: Mark true if the comment is spam, irrelevant, repetitive, misleading, self-promoting, or fails minimum quality standards. -- requiresAdminReview: Mark true ONLY if the comment reports: service non-functionality, listing inaccuracies, clear scams, exit-scams, critical policy changes, malfunctions, service outages, or sensitive platform issues. If true, always add internalNote to explain why you made this decision. -- contextNote: Optional, visible to users. Add ONLY when clarification or warning is necessary―e.g., unsubstantiated claims or potential spam. -- internalNote: Internal note that is not visible to users. Example: explain why you marked a comment as spam or low quality. You should leave this empty if no relevant information would be added. -- commentQuality: 0 (lowest) to 10 (highest). Rate purely on informativeness, relevance, helpfulness, and evidence. - -## STRICT MODERATION RULES - -- Reject ALL comments that are generic, extremely short, or meaningless on their own, unless replying with added value or genuine context. Examples: "hey", "hello", "hi", "ok", "good", "great", "thanks", "test", "scam"—these are LOW quality and must generally be flagged as spam or rated VERY low, unless context justifies. - - Exception: Replies allowed if they significantly clarify, elaborate, or engage with a previous comment, and ADD new value. -- Comments must provide context, detail, experience, a clear perspective, or evidence. Approve only if the comment adds meaningful insight to the listing’s discussion. -- Mark as spam: - - Meaningless, contextless, very short comments (“hi”, “hey”). - - Comments entirely self-promotional, containing excessive emojis, special characters, random text, or multiple unrelated links. -- Use the surrounding context (such as parent comments, service description, previous discussions) to evaluate if a short comment is a valid reply, or still too low quality to approve. -- Rate "commentQuality" based on: - - 0-2: Meaningless, off-topic, one-word, no value. - - 3-5: Vague, minimal, only slightly relevant, lacking evidence. - - 6-8: Detailed, relevant, some insight or evidence, well-explained. - - 9-10: Exceptionally thorough, informative, well-documented experience. -- For claims (positive or negative) without evidence, add a warning context note: "This comment makes claims without supporting evidence." -- For extended, unstructured, or incoherent text (e.g. spam, or AI-generated nonsense), mark as spam. - -## EXAMPLES - -- "hello": - isSpam: true, internalNote: "Comment provides no value or context.", commentQuality: 0 -- "works": - isSpam: true, internalNote: "Comment too short and contextless.", commentQuality: 0 -- "Service did not work on my device—got error 503.": - isSpam: false, requiresAdminReview: true, commentQuality: 7 -- "Scam!": - isSpam: true, internalNote: "Unsubstantiated, one-word negative claim.", commentQuality: 0, contextNote: "This is a one-word claim without details or evidence." -- "Instant transactions, responsive customer support. Used for 6 months.": - isSpam: false, commentQuality: 8 - -## INSTRUCTIONS - -- Always evaluate if a comment stands on its own, adds value, and has relevance to the listing. Reject one-word, contextless, or “drive-by” comments. -- Replies: Only approve short replies if they directly answer or clarify something above and ADD useful new information. - -Format your output EXACTLY as a raw JSON object using the schema, with NO extra formatting, markdown, or text. -""" diff --git a/pyworker/pyworker/utils/app_http.py b/pyworker/pyworker/utils/app_http.py deleted file mode 100644 index 1edd4ee..0000000 --- a/pyworker/pyworker/utils/app_http.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -HTTP utilities for the pyworker package. -""" - -from typing import Optional - -import requests - -from pyworker.utils.app_logging import setup_logging - -logger = setup_logging(__name__) - - -def fetch_url(url: str, timeout: int = 30) -> Optional[str]: - """ - Fetch content from a URL. - - Args: - url: The URL to fetch. - timeout: The timeout in seconds. - - Returns: - The text content of the response, or None if the request failed. - """ - try: - response = requests.get(url, timeout=timeout) - response.raise_for_status() - return response.text - except requests.RequestException as e: - logger.error(f"Error fetching URL {url}: {e}") - return None diff --git a/pyworker/pyworker/utils/app_logging.py b/pyworker/pyworker/utils/app_logging.py deleted file mode 100644 index cb31d8e..0000000 --- a/pyworker/pyworker/utils/app_logging.py +++ /dev/null @@ -1,36 +0,0 @@ -""" -Logging utilities for the pyworker package. -""" - -import logging -import sys -from pyworker.config import config - -def setup_logging(name: str = "pyworker") -> logging.Logger: - """ - Set up logging for the application. - - Args: - name: The name of the logger. - - Returns: - A configured logger instance. - """ - logger = logging.getLogger(name) - - # Set log level from configuration - log_level = getattr(logging, config.LOG_LEVEL.upper(), logging.INFO) - logger.setLevel(log_level) - - # Create console handler - handler = logging.StreamHandler(sys.stdout) - handler.setLevel(log_level) - - # Create formatter - formatter = logging.Formatter(config.LOG_FORMAT) - handler.setFormatter(formatter) - - # Add handler to logger - logger.addHandler(handler) - - return logger \ No newline at end of file diff --git a/pyworker/pyworker/utils/crawl.py b/pyworker/pyworker/utils/crawl.py deleted file mode 100644 index f2bf6e1..0000000 --- a/pyworker/pyworker/utils/crawl.py +++ /dev/null @@ -1,100 +0,0 @@ -import argparse -import os -import time -import requests -from dotenv import load_dotenv -from pyworker.utils.app_logging import setup_logging -from typing import Any - -logger = setup_logging(__name__) - - -# Load environment variables from .env file -load_dotenv() - -# Include API token header if set -CRAWL4AI_API_TOKEN = os.environ.get("CRAWL4AI_API_TOKEN", "") -HEADERS = ( - {"Authorization": f"Bearer {CRAWL4AI_API_TOKEN}"} if CRAWL4AI_API_TOKEN else {} -) - -CRAWL4AI_BASE_URL = os.environ.get("CRAWL4AI_BASE_URL", "http://crawl4ai:11235") -CRAWL4AI_TIMEOUT = int(os.environ.get("CRAWL4AI_TIMEOUT", 300)) -CRAWL4AI_POLL_INTERVAL = int(os.environ.get("CRAWL4AI_POLL_INTERVAL", 2)) - - -def fetch_fallback(url: str) -> str: - if not url: - raise ValueError("URL must not be empty") - logger.info(f"Fetching fallback for {url}") - fallback_url = f"https://r.jina.ai/{url.lstrip('/')}" - response = requests.get(fallback_url, timeout=80) - response.raise_for_status() - return response.text - - -def fetch_markdown(url: str, wait_for_dynamic_content: bool = True) -> str: - if not CRAWL4AI_API_TOKEN: - return fetch_fallback(url) - - try: - payload: dict[str, Any] = {"urls": url} - if wait_for_dynamic_content: - # According to Crawl4AI docs, wait_for_images=True also waits for network idle state, - # which is helpful for JS-generated content. - # Adding scan_full_page and scroll_delay helps trigger lazy-loaded content. - payload["config"] = { - "wait_for_images": True, - "scan_full_page": True, - "scroll_delay": 0.5, - "magic": True, - } - - response = requests.post( - f"{CRAWL4AI_BASE_URL}/crawl", - json=payload, - headers=HEADERS, - ) - response.raise_for_status() - task_id = response.json().get("task_id") - start_time = time.time() - while True: - if time.time() - start_time > CRAWL4AI_TIMEOUT: - raise TimeoutError(f"Task {task_id} timeout") - status_resp = requests.get( - f"{CRAWL4AI_BASE_URL}/task/{task_id}", - headers=HEADERS, - ) - status_resp.raise_for_status() - status = status_resp.json() - if status.get("status") == "completed": - markdown = status["result"].get("markdown", "") - metadata = status["result"].get("metadata", {}) - return f""" -URL: {url} -Page Metadata: `{metadata}` - -Markdown Content ----------------- -{markdown} - """ - time.sleep(CRAWL4AI_POLL_INTERVAL) - except (requests.exceptions.RequestException, TimeoutError): - return fetch_fallback(url) - - -def main(): - parser = argparse.ArgumentParser( - description="Crawl a URL and print its markdown content." - ) - parser.add_argument("--url", required=True, help="The URL to crawl") - - args = parser.parse_args() - print(f"Crawling {args.url}...") - markdown_content = fetch_markdown(args.url) - print("\n--- Markdown Content ---") - print(markdown_content) - - -if __name__ == "__main__": - main() diff --git a/pyworker/tests/__init__.py b/pyworker/tests/__init__.py deleted file mode 100644 index da44c7f..0000000 --- a/pyworker/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Test package for the pyworker.""" \ No newline at end of file diff --git a/pyworker/tests/test_tasks.py b/pyworker/tests/test_tasks.py deleted file mode 100644 index a5c2c51..0000000 --- a/pyworker/tests/test_tasks.py +++ /dev/null @@ -1,74 +0,0 @@ -""" -Tests for task modules. -""" - -import unittest -from unittest.mock import patch, MagicMock -from typing import Dict, Any - -from pyworker.tasks import TosReviewTask - -class TestTosRetrievalTask(unittest.TestCase): - """Tests for the TOS retrieval task.""" - - def setUp(self): - """Set up test fixtures.""" - self.task = TosReviewTask() - self.service = { - 'id': 1, - 'name': 'Test Service', - 'tosUrls': ['test1', 'test2'] - } - - @patch('pyworker.tasks.tos_review.fetch_url') - def test_run_success(self, mock_fetch_url: MagicMock) -> None: - """Test successful TOS retrieval.""" - # Mock the fetch_url function to return test responses - mock_fetch_url.side_effect = ["Test TOS 1", "Test TOS 2"] - - # Run the task - result = self.task.run(self.service) - - # Check that the function was called twice with the correct arguments - self.assertEqual(mock_fetch_url.call_count, 2) - mock_fetch_url.assert_any_call('https://r.jina.ai/test1') - mock_fetch_url.assert_any_call('https://r.jina.ai/test2') - - # Check that the result contains the expected content - self.assertEqual(result, { - 'test1': 'Test TOS 1', - 'test2': 'Test TOS 2' - }) - - @patch('pyworker.tasks.tos_review.fetch_url') - def test_run_failure(self, mock_fetch_url: MagicMock) -> None: - """Test TOS retrieval failure.""" - # Mock the fetch_url function to return None (failure) - mock_fetch_url.return_value = None - - # Run the task - result = self.task.run(self.service) - - # Check that the function was called twice - self.assertEqual(mock_fetch_url.call_count, 2) - - # Check that the result is None since all fetches failed - self.assertIsNone(result) - - def test_run_no_urls(self): - """Test TOS retrieval with no URLs.""" - # Create a service with no TOS URLs - service_no_urls: Dict[str, Any] = { - 'id': 2, - 'name': 'Service With No TOS', - 'tosUrls': [] - } - - # Run the task - result = self.task.run(service_no_urls) - - # Check that the result is None - self.assertIsNone(result) - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/pyworker/uv.lock b/pyworker/uv.lock deleted file mode 100644 index 9ee010a..0000000 --- a/pyworker/uv.lock +++ /dev/null @@ -1,414 +0,0 @@ -version = 1 -revision = 1 -requires-python = ">=3.13" - -[[package]] -name = "annotated-types" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, -] - -[[package]] -name = "anyio" -version = "4.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "idna" }, - { name = "sniffio" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, -] - -[[package]] -name = "certifi" -version = "2025.1.31" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, - { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, - { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, - { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, - { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, - { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, - { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, - { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, - { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, - { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, - { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, - { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, - { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, - { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, -] - -[[package]] -name = "croniter" -version = "6.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "python-dateutil" }, - { name = "pytz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ad/2f/44d1ae153a0e27be56be43465e5cb39b9650c781e001e7864389deb25090/croniter-6.0.0.tar.gz", hash = "sha256:37c504b313956114a983ece2c2b07790b1f1094fe9d81cc94739214748255577", size = 64481 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/4b/290b4c3efd6417a8b0c284896de19b1d5855e6dbdb97d2a35e68fa42de85/croniter-6.0.0-py2.py3-none-any.whl", hash = "sha256:2f878c3856f17896979b2a4379ba1f09c83e374931ea15cc835c5dd2eee9b368", size = 25468 }, -] - -[[package]] -name = "distro" -version = "1.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 }, -] - -[[package]] -name = "h11" -version = "0.14.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, -] - -[[package]] -name = "httpcore" -version = "1.0.8" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9f/45/ad3e1b4d448f22c0cff4f5692f5ed0666658578e358b8d58a19846048059/httpcore-1.0.8.tar.gz", hash = "sha256:86e94505ed24ea06514883fd44d2bc02d90e77e7979c8eb71b90f41d364a1bad", size = 85385 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/8d/f052b1e336bb2c1fc7ed1aaed898aa570c0b61a09707b108979d9fc6e308/httpcore-1.0.8-py3-none-any.whl", hash = "sha256:5254cf149bcb5f75e9d1b2b9f729ea4a4b883d1ad7379fc632b727cec23674be", size = 78732 }, -] - -[[package]] -name = "httpx" -version = "0.28.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, -] - -[[package]] -name = "idna" -version = "3.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, -] - -[[package]] -name = "jiter" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/c2/e4562507f52f0af7036da125bb699602ead37a2332af0788f8e0a3417f36/jiter-0.9.0.tar.gz", hash = "sha256:aadba0964deb424daa24492abc3d229c60c4a31bfee205aedbf1acc7639d7893", size = 162604 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/1b/4cd165c362e8f2f520fdb43245e2b414f42a255921248b4f8b9c8d871ff1/jiter-0.9.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2764891d3f3e8b18dce2cff24949153ee30c9239da7c00f032511091ba688ff7", size = 308197 }, - { url = "https://files.pythonhosted.org/packages/13/aa/7a890dfe29c84c9a82064a9fe36079c7c0309c91b70c380dc138f9bea44a/jiter-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:387b22fbfd7a62418d5212b4638026d01723761c75c1c8232a8b8c37c2f1003b", size = 318160 }, - { url = "https://files.pythonhosted.org/packages/6a/38/5888b43fc01102f733f085673c4f0be5a298f69808ec63de55051754e390/jiter-0.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d8da8629ccae3606c61d9184970423655fb4e33d03330bcdfe52d234d32f69", size = 341259 }, - { url = "https://files.pythonhosted.org/packages/3d/5e/bbdbb63305bcc01006de683b6228cd061458b9b7bb9b8d9bc348a58e5dc2/jiter-0.9.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1be73d8982bdc278b7b9377426a4b44ceb5c7952073dd7488e4ae96b88e1103", size = 363730 }, - { url = "https://files.pythonhosted.org/packages/75/85/53a3edc616992fe4af6814c25f91ee3b1e22f7678e979b6ea82d3bc0667e/jiter-0.9.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2228eaaaa111ec54b9e89f7481bffb3972e9059301a878d085b2b449fbbde635", size = 405126 }, - { url = "https://files.pythonhosted.org/packages/ae/b3/1ee26b12b2693bd3f0b71d3188e4e5d817b12e3c630a09e099e0a89e28fa/jiter-0.9.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:11509bfecbc319459647d4ac3fd391d26fdf530dad00c13c4dadabf5b81f01a4", size = 393668 }, - { url = "https://files.pythonhosted.org/packages/11/87/e084ce261950c1861773ab534d49127d1517b629478304d328493f980791/jiter-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f22238da568be8bbd8e0650e12feeb2cfea15eda4f9fc271d3b362a4fa0604d", size = 352350 }, - { url = "https://files.pythonhosted.org/packages/f0/06/7dca84b04987e9df563610aa0bc154ea176e50358af532ab40ffb87434df/jiter-0.9.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17f5d55eb856597607562257c8e36c42bc87f16bef52ef7129b7da11afc779f3", size = 384204 }, - { url = "https://files.pythonhosted.org/packages/16/2f/82e1c6020db72f397dd070eec0c85ebc4df7c88967bc86d3ce9864148f28/jiter-0.9.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:6a99bed9fbb02f5bed416d137944419a69aa4c423e44189bc49718859ea83bc5", size = 520322 }, - { url = "https://files.pythonhosted.org/packages/36/fd/4f0cd3abe83ce208991ca61e7e5df915aa35b67f1c0633eb7cf2f2e88ec7/jiter-0.9.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e057adb0cd1bd39606100be0eafe742de2de88c79df632955b9ab53a086b3c8d", size = 512184 }, - { url = "https://files.pythonhosted.org/packages/a0/3c/8a56f6d547731a0b4410a2d9d16bf39c861046f91f57c98f7cab3d2aa9ce/jiter-0.9.0-cp313-cp313-win32.whl", hash = "sha256:f7e6850991f3940f62d387ccfa54d1a92bd4bb9f89690b53aea36b4364bcab53", size = 206504 }, - { url = "https://files.pythonhosted.org/packages/f4/1c/0c996fd90639acda75ed7fa698ee5fd7d80243057185dc2f63d4c1c9f6b9/jiter-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:c8ae3bf27cd1ac5e6e8b7a27487bf3ab5f82318211ec2e1346a5b058756361f7", size = 204943 }, - { url = "https://files.pythonhosted.org/packages/78/0f/77a63ca7aa5fed9a1b9135af57e190d905bcd3702b36aca46a01090d39ad/jiter-0.9.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f0b2827fb88dda2cbecbbc3e596ef08d69bda06c6f57930aec8e79505dc17001", size = 317281 }, - { url = "https://files.pythonhosted.org/packages/f9/39/a3a1571712c2bf6ec4c657f0d66da114a63a2e32b7e4eb8e0b83295ee034/jiter-0.9.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062b756ceb1d40b0b28f326cba26cfd575a4918415b036464a52f08632731e5a", size = 350273 }, - { url = "https://files.pythonhosted.org/packages/ee/47/3729f00f35a696e68da15d64eb9283c330e776f3b5789bac7f2c0c4df209/jiter-0.9.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6f7838bc467ab7e8ef9f387bd6de195c43bad82a569c1699cb822f6609dd4cdf", size = 206867 }, -] - -[[package]] -name = "json-repair" -version = "0.41.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6d/6a/6c7a75a10da6dc807b582f2449034da1ed74415e8899746bdfff97109012/json_repair-0.41.1.tar.gz", hash = "sha256:bba404b0888c84a6b86ecc02ec43b71b673cfee463baf6da94e079c55b136565", size = 31208 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/10/5c/abd7495c934d9af5c263c2245ae30cfaa716c3c0cf027b2b8fa686ee7bd4/json_repair-0.41.1-py3-none-any.whl", hash = "sha256:0e181fd43a696887881fe19fed23422a54b3e4c558b6ff27a86a8c3ddde9ae79", size = 21578 }, -] - -[[package]] -name = "openai" -version = "1.74.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "distro" }, - { name = "httpx" }, - { name = "jiter" }, - { name = "pydantic" }, - { name = "sniffio" }, - { name = "tqdm" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/75/86/c605a6e84da0248f2cebfcd864b5a6076ecf78849245af5e11d2a5ec7977/openai-1.74.0.tar.gz", hash = "sha256:592c25b8747a7cad33a841958f5eb859a785caea9ee22b9e4f4a2ec062236526", size = 427571 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/91/8c150f16a96367e14bd7d20e86e0bbbec3080e3eb593e63f21a7f013f8e4/openai-1.74.0-py3-none-any.whl", hash = "sha256:aff3e0f9fb209836382ec112778667027f4fd6ae38bdb2334bc9e173598b092a", size = 644790 }, -] - -[[package]] -name = "psycopg" -version = "3.2.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "tzdata", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/67/97/eea08f74f1c6dd2a02ee81b4ebfe5b558beb468ebbd11031adbf58d31be0/psycopg-3.2.6.tar.gz", hash = "sha256:16fa094efa2698f260f2af74f3710f781e4a6f226efe9d1fd0c37f384639ed8a", size = 156322 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/7d/0ba52deff71f65df8ec8038adad86ba09368c945424a9bd8145d679a2c6a/psycopg-3.2.6-py3-none-any.whl", hash = "sha256:f3ff5488525890abb0566c429146add66b329e20d6d4835662b920cbbf90ac58", size = 199077 }, -] - -[package.optional-dependencies] -binary = [ - { name = "psycopg-binary", marker = "implementation_name != 'pypy'" }, -] -pool = [ - { name = "psycopg-pool" }, -] - -[[package]] -name = "psycopg-binary" -version = "3.2.6" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/32/3d06c478fd3070ac25a49c2e8ca46b6d76b0048fa9fa255b99ee32f32312/psycopg_binary-3.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54af3fbf871baa2eb19df96fd7dc0cbd88e628a692063c3d1ab5cdd00aa04322", size = 3852672 }, - { url = "https://files.pythonhosted.org/packages/34/97/e581030e279500ede3096adb510f0e6071874b97cfc047a9a87b7d71fc77/psycopg_binary-3.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ad5da1e4636776c21eaeacdec42f25fa4612631a12f25cd9ab34ddf2c346ffb9", size = 3936562 }, - { url = "https://files.pythonhosted.org/packages/74/b6/6a8df4cb23c3d327403a83406c06c9140f311cb56c4e4d720ee7abf6fddc/psycopg_binary-3.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7956b9ea56f79cd86eddcfbfc65ae2af1e4fe7932fa400755005d903c709370", size = 4499167 }, - { url = "https://files.pythonhosted.org/packages/e4/5b/950eafef61e5e0b8ddb5afc5b6b279756411aa4bf70a346a6f091ad679bb/psycopg_binary-3.2.6-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e2efb763188008cf2914820dcb9fb23c10fe2be0d2c97ef0fac7cec28e281d8", size = 4311651 }, - { url = "https://files.pythonhosted.org/packages/72/b9/b366c49afc854c26b3053d4d35376046eea9aebdc48ded18ea249ea1f80c/psycopg_binary-3.2.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b3aab3451679f1e7932270e950259ed48c3b79390022d3f660491c0e65e4838", size = 4547852 }, - { url = "https://files.pythonhosted.org/packages/ab/d4/0e047360e2ea387dc7171ca017ffcee5214a0762f74b9dd982035f2e52fb/psycopg_binary-3.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:849a370ac4e125f55f2ad37f928e588291a67ccf91fa33d0b1e042bb3ee1f986", size = 4261725 }, - { url = "https://files.pythonhosted.org/packages/e3/ea/a1b969804250183900959ebe845d86be7fed2cbd9be58f64cd0fc24b2892/psycopg_binary-3.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:566d4ace928419d91f1eb3227fc9ef7b41cf0ad22e93dd2c3368d693cf144408", size = 3850073 }, - { url = "https://files.pythonhosted.org/packages/e5/71/ec2907342f0675092b76aea74365b56f38d960c4c635984dcfe25d8178c8/psycopg_binary-3.2.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f1981f13b10de2f11cfa2f99a8738b35b3f0a0f3075861446894a8d3042430c0", size = 3320323 }, - { url = "https://files.pythonhosted.org/packages/d7/d7/0d2cb4b42f231e2efe8ea1799ce917973d47486212a2c4d33cd331e7ac28/psycopg_binary-3.2.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:36f598300b55b3c983ae8df06473ad27333d2fd9f3e2cfdb913b3a5aaa3a8bcf", size = 3402335 }, - { url = "https://files.pythonhosted.org/packages/66/92/7050c372f78e53eba14695cec6c3a91b2d9ca56feaf0bfe95fe90facf730/psycopg_binary-3.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0f4699fa5fe1fffb0d6b2d14b31fd8c29b7ea7375f89d5989f002aaf21728b21", size = 3440442 }, - { url = "https://files.pythonhosted.org/packages/5f/4c/bebcaf754189283b2f3d457822a3d9b233d08ff50973d8f1e8d51f4d35ed/psycopg_binary-3.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:afe697b8b0071f497c5d4c0f41df9e038391534f5614f7fb3a8c1ca32d66e860", size = 2783465 }, -] - -[[package]] -name = "psycopg-pool" -version = "3.2.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cf/13/1e7850bb2c69a63267c3dbf37387d3f71a00fd0e2fa55c5db14d64ba1af4/psycopg_pool-3.2.6.tar.gz", hash = "sha256:0f92a7817719517212fbfe2fd58b8c35c1850cdd2a80d36b581ba2085d9148e5", size = 29770 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/47/fd/4feb52a55c1a4bd748f2acaed1903ab54a723c47f6d0242780f4d97104d4/psycopg_pool-3.2.6-py3-none-any.whl", hash = "sha256:5887318a9f6af906d041a0b1dc1c60f8f0dda8340c2572b74e10907b51ed5da7", size = 38252 }, -] - -[[package]] -name = "pydantic" -version = "2.11.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/10/2e/ca897f093ee6c5f3b0bee123ee4465c50e75431c3d5b6a3b44a47134e891/pydantic-2.11.3.tar.gz", hash = "sha256:7471657138c16adad9322fe3070c0116dd6c3ad8d649300e3cbdfe91f4db4ec3", size = 785513 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/1d/407b29780a289868ed696d1616f4aad49d6388e5a77f567dcd2629dcd7b8/pydantic-2.11.3-py3-none-any.whl", hash = "sha256:a082753436a07f9ba1289c6ffa01cd93db3548776088aa917cc43b63f68fa60f", size = 443591 }, -] - -[[package]] -name = "pydantic-core" -version = "2.33.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/17/19/ed6a078a5287aea7922de6841ef4c06157931622c89c2a47940837b5eecd/pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df", size = 434395 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/24/eed3466a4308d79155f1cdd5c7432c80ddcc4530ba8623b79d5ced021641/pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a", size = 2033551 }, - { url = "https://files.pythonhosted.org/packages/ab/14/df54b1a0bc9b6ded9b758b73139d2c11b4e8eb43e8ab9c5847c0a2913ada/pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266", size = 1852785 }, - { url = "https://files.pythonhosted.org/packages/fa/96/e275f15ff3d34bb04b0125d9bc8848bf69f25d784d92a63676112451bfb9/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3", size = 1897758 }, - { url = "https://files.pythonhosted.org/packages/b7/d8/96bc536e975b69e3a924b507d2a19aedbf50b24e08c80fb00e35f9baaed8/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a", size = 1986109 }, - { url = "https://files.pythonhosted.org/packages/90/72/ab58e43ce7e900b88cb571ed057b2fcd0e95b708a2e0bed475b10130393e/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516", size = 2129159 }, - { url = "https://files.pythonhosted.org/packages/dc/3f/52d85781406886c6870ac995ec0ba7ccc028b530b0798c9080531b409fdb/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764", size = 2680222 }, - { url = "https://files.pythonhosted.org/packages/f4/56/6e2ef42f363a0eec0fd92f74a91e0ac48cd2e49b695aac1509ad81eee86a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d", size = 2006980 }, - { url = "https://files.pythonhosted.org/packages/4c/c0/604536c4379cc78359f9ee0aa319f4aedf6b652ec2854953f5a14fc38c5a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4", size = 2120840 }, - { url = "https://files.pythonhosted.org/packages/1f/46/9eb764814f508f0edfb291a0f75d10854d78113fa13900ce13729aaec3ae/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde", size = 2072518 }, - { url = "https://files.pythonhosted.org/packages/42/e3/fb6b2a732b82d1666fa6bf53e3627867ea3131c5f39f98ce92141e3e3dc1/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e", size = 2248025 }, - { url = "https://files.pythonhosted.org/packages/5c/9d/fbe8fe9d1aa4dac88723f10a921bc7418bd3378a567cb5e21193a3c48b43/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd", size = 2254991 }, - { url = "https://files.pythonhosted.org/packages/aa/99/07e2237b8a66438d9b26482332cda99a9acccb58d284af7bc7c946a42fd3/pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f", size = 1915262 }, - { url = "https://files.pythonhosted.org/packages/8a/f4/e457a7849beeed1e5defbcf5051c6f7b3c91a0624dd31543a64fc9adcf52/pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40", size = 1956626 }, - { url = "https://files.pythonhosted.org/packages/20/d0/e8d567a7cff7b04e017ae164d98011f1e1894269fe8e90ea187a3cbfb562/pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523", size = 1909590 }, - { url = "https://files.pythonhosted.org/packages/ef/fd/24ea4302d7a527d672c5be06e17df16aabfb4e9fdc6e0b345c21580f3d2a/pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d", size = 1812963 }, - { url = "https://files.pythonhosted.org/packages/5f/95/4fbc2ecdeb5c1c53f1175a32d870250194eb2fdf6291b795ab08c8646d5d/pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c", size = 1986896 }, - { url = "https://files.pythonhosted.org/packages/71/ae/fe31e7f4a62431222d8f65a3bd02e3fa7e6026d154a00818e6d30520ea77/pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18", size = 1931810 }, -] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, -] - -[[package]] -name = "python-dotenv" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 }, -] - -[[package]] -name = "pytz" -version = "2025.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225 }, -] - -[[package]] -name = "pyworker" -version = "0.1.0" -source = { editable = "." } -dependencies = [ - { name = "croniter" }, - { name = "json-repair" }, - { name = "openai" }, - { name = "psycopg", extra = ["binary", "pool"] }, - { name = "python-dotenv" }, - { name = "requests" }, -] - -[package.metadata] -requires-dist = [ - { name = "croniter", specifier = ">=6.0.0" }, - { name = "json-repair", specifier = ">=0.41.1" }, - { name = "openai", specifier = ">=1.74.0" }, - { name = "psycopg", extras = ["binary", "pool"], specifier = ">=3.2.6" }, - { name = "python-dotenv", specifier = ">=1.1.0" }, - { name = "requests", specifier = ">=2.32.3" }, -] - -[[package]] -name = "requests" -version = "2.32.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, -] - -[[package]] -name = "six" -version = "1.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, -] - -[[package]] -name = "tqdm" -version = "4.67.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, -] - -[[package]] -name = "typing-extensions" -version = "4.13.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 }, -] - -[[package]] -name = "typing-inspection" -version = "0.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 }, -] - -[[package]] -name = "tzdata" -version = "2025.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 }, -] - -[[package]] -name = "urllib3" -version = "2.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680 }, -] diff --git a/web/.dockerignore b/web/.dockerignore deleted file mode 100644 index 15434de..0000000 --- a/web/.dockerignore +++ /dev/null @@ -1,28 +0,0 @@ -# build output -dist/ -# generated types -.astro/ - -# dependencies -node_modules/ - -# local only data -local_data/ - -# logs -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* - - -# environment variables -.env.production - -# macOS-specific files -.DS_Store - -# jetbrains setting folder -.idea/ - -*.example \ No newline at end of file diff --git a/web/.env.example b/web/.env.example deleted file mode 100644 index 56f0455..0000000 --- a/web/.env.example +++ /dev/null @@ -1,4 +0,0 @@ -DATABASE_URL="postgresql://kycnot:kycnot@localhost:3399/kycnot?schema=public" -REDIS_URL="redis://localhost:6379" -SOURCE_CODE_URL="https://github.com" -SITE_URL="https://localhost:4321" \ No newline at end of file diff --git a/web/.gitignore b/web/.gitignore deleted file mode 100644 index 3d9f235..0000000 --- a/web/.gitignore +++ /dev/null @@ -1,31 +0,0 @@ -# build output -dist/ -# generated types -.astro/ - -# dependencies -node_modules/ - -# local only data -local_data/ - -# logs -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* - - -# environment variables -.env -.env.production - -# macOS-specific files -.DS_Store - -# jetbrains setting folder -.idea/ - -local_uploads/ -!local_uploads/.gitkeep -uploads/ \ No newline at end of file diff --git a/web/.npmrc b/web/.npmrc deleted file mode 100644 index cffe8cd..0000000 --- a/web/.npmrc +++ /dev/null @@ -1 +0,0 @@ -save-exact=true diff --git a/web/.nvmrc b/web/.nvmrc deleted file mode 100644 index 4099407..0000000 --- a/web/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -23 diff --git a/web/.prettierignore b/web/.prettierignore deleted file mode 100644 index c2c7bde..0000000 --- a/web/.prettierignore +++ /dev/null @@ -1,5 +0,0 @@ -web/public/ -.git/ -package-lock.json -local_data/ -.astro/ diff --git a/web/.prettierrc.mjs b/web/.prettierrc.mjs deleted file mode 100644 index a03aa12..0000000 --- a/web/.prettierrc.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// @ts-check - -/** @type {import("prettier").Config} */ -export default { - plugins: ['prettier-plugin-astro', 'prettier-plugin-tailwindcss'], - overrides: [ - { - files: '*.astro', - options: { - parser: 'astro', - }, - }, - ], - tailwindFunctions: ['cn', 'clsx', 'tv'], - singleQuote: true, - semi: false, - tabWidth: 2, - trailingComma: 'es5', - printWidth: 110, - bracketSpacing: true, - endOfLine: 'lf', -} diff --git a/web/Dockerfile b/web/Dockerfile deleted file mode 100644 index 07585de..0000000 --- a/web/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -FROM node:lts AS runtime -WORKDIR /app - -COPY package.json package-lock.json ./ - -COPY .npmrc .npmrc - -RUN npm ci - -COPY . . - -ARG ASTRO_BUILD_MODE=production -# Generate Prisma client -RUN npx prisma generate -# Build the application -RUN npm run build -- --mode ${ASTRO_BUILD_MODE} - -ENV HOST=0.0.0.0 -ENV PORT=4321 -EXPOSE 4321 - -# Add entrypoint script and make it executable -COPY docker-entrypoint.sh /usr/local/bin/ -RUN chmod +x /usr/local/bin/docker-entrypoint.sh - -ENTRYPOINT ["docker-entrypoint.sh"] -CMD ["node", "./dist/server/entry.mjs"] diff --git a/web/README.md b/web/README.md deleted file mode 100644 index adef8cd..0000000 --- a/web/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# KYCnot.me website - -[KYCnot.me](https://kycnot.me) - -## Commands - -All commands are run from the root of the project, from a terminal: - -| Command | Action | -| :------------------------ | :------------------------------------------------------------------- | -| `nvm install` | Installs and uses the correct version of node | -| `npm install` | Installs dependencies | -| `npm run dev` | Starts local dev server at `localhost:4321` | -| `npm run build` | Build your production site to `./dist/` | -| `npm run preview` | Preview your build locally, before deploying | -| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | -| `npm run astro -- --help` | Get help using the Astro CLI | -| `npm run db-admin` | Runs Prisma Studio (database admin) | -| `npm run db-gen` | Generates the Prisma client without running migrations | -| `npm run db-push` | Updates the database schema with latest changes (development mode). | -| `npm run db-fill` | Fills the database with fake data (development mode) | -| `npm run db-fill-clean` | Cleans existing data and fills with new fake data (development mode) | -| `npm run format` | Formats the code with Prettier | -| `npm run lint` | Lints the code with ESLint | -| `npm run lint-fix` | Lints the code with ESLint and fixes the issues | - -> **Note**: `db-fill` and `db-fill-clean` support the `-- --services=n` flag, where n is the number of fake services to add. It defaults to 10. For example, `npm run db-fill -- --services=5` will add 5 fake services. - -> **Note**: `db-fill` and `db-fill-clean` create default users with tokens: `admin`, `verifier`, `verified`, `normal` (override with `DEV_*****_USER_SECRET_TOKEN` env vars) diff --git a/web/astro.config.mjs b/web/astro.config.mjs deleted file mode 100644 index b33947f..0000000 --- a/web/astro.config.mjs +++ /dev/null @@ -1,159 +0,0 @@ -// @ts-check - -import mdx from '@astrojs/mdx' -import node from '@astrojs/node' -import sitemap from '@astrojs/sitemap' -import tailwindcss from '@tailwindcss/vite' -import { defineConfig, envField } from 'astro/config' -import icon from 'astro-icon' -import { loadEnv } from 'vite' - -// @ts-expect-error process.env actually exists -const { SITE_URL } = loadEnv(process.env.NODE_ENV, process.cwd(), '') -if (!SITE_URL) throw new Error('SITE_URL environment variable is not set') - -export default defineConfig({ - site: SITE_URL, - vite: { - build: { - sourcemap: true, - }, - - plugins: [tailwindcss()], - }, - integrations: [ - icon(), - mdx(), - sitemap({ - filter: (page) => { - const url = new URL(page) - return !url.pathname.startsWith('/admin') && !url.pathname.startsWith('/account/impersonate') - }, - }), - ], - adapter: node({ - mode: 'standalone', - }), - output: 'server', - devToolbar: { - enabled: false, - }, - server: { - open: false, - allowedHosts: [new URL(SITE_URL).hostname], - }, - redirects: { - // #region Redirects from old website - '/pending': '/?verification=verified&verification=approved&verification=community', - '/changelog': '/events', - '/request': '/service-suggestion/new', - '/service/[...slug]/summary': '/service/[...slug]/#scores', - '/service/[...slug]/proof': '/service/[...slug]/#verification', - '/attribute/[...slug]': '/attributes', - '/attr/[...slug]': '/attributes', - // #endregion - }, - env: { - schema: { - // Database (server-only, secret) - DATABASE_URL: envField.string({ - context: 'server', - access: 'secret', - url: true, - startsWith: 'postgresql://', - default: 'postgresql://kycnot:kycnot@database:5432/kycnot?schema=public', - }), - // Public URLs (can be accessed from both server and client) - SOURCE_CODE_URL: envField.string({ - context: 'server', - access: 'public', - url: true, - optional: false, - }), - - REDIS_URL: envField.string({ - context: 'server', - access: 'secret', - url: true, - startsWith: 'redis://', - default: 'redis://redis:6379', - }), - REDIS_USER_SESSION_EXPIRY_SECONDS: envField.number({ - context: 'server', - access: 'secret', - int: true, - gt: 0, - default: 60 * 60 * 24, // 24 hours in seconds - }), - REDIS_IMPERSONATION_SESSION_EXPIRY_SECONDS: envField.number({ - context: 'server', - access: 'secret', - int: true, - gt: 0, - default: 60 * 60 * 24, // 24 hours in seconds - }), - REDIS_PREGENERATED_TOKEN_EXPIRY_SECONDS: envField.number({ - context: 'server', - access: 'secret', - int: true, - gt: 0, - default: 60 * 5, // 5 minutes in seconds - }), - - REDIS_ACTIONS_SESSION_EXPIRY_SECONDS: envField.number({ - context: 'server', - access: 'secret', - int: true, - gt: 0, - default: 60 * 5, // 5 minutes in seconds - }), - - // Development tokens - DEV_ADMIN_USER_SECRET_TOKEN: envField.string({ - context: 'server', - access: 'secret', - min: 1, - default: 'admin', - }), - DEV_VERIFIER_USER_SECRET_TOKEN: envField.string({ - context: 'server', - access: 'secret', - min: 1, - default: 'verifier', - }), - DEV_VERIFIED_USER_SECRET_TOKEN: envField.string({ - context: 'server', - access: 'secret', - min: 1, - default: 'verified', - }), - DEV_NORMAL_USER_SECRET_TOKEN: envField.string({ - context: 'server', - access: 'secret', - min: 1, - default: 'normal', - }), - DEV_SPAM_USER_SECRET_TOKEN: envField.string({ - context: 'server', - access: 'secret', - min: 1, - default: 'spam', - }), - - // Upload directory configuration - UPLOAD_DIR: envField.string({ - context: 'server', - access: 'secret', - min: 1, - default: './local_uploads', - }), - - SITE_URL: envField.string({ - context: 'client', - access: 'public', - url: true, - optional: false, - }), - }, - }, -}) diff --git a/web/docker-entrypoint.sh b/web/docker-entrypoint.sh deleted file mode 100644 index 8396032..0000000 --- a/web/docker-entrypoint.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh -set -e - -# Apply migrations -echo "Applying database migrations..." -npx prisma migrate deploy - -# Apply triggers -echo "Applying database triggers..." -for trigger_file in prisma/triggers/*.sql; do - if [ -f "$trigger_file" ]; then - echo "Applying trigger: $trigger_file" - npx prisma db execute --file "$trigger_file" --schema=./prisma/schema.prisma - else - echo "No trigger files found in prisma/triggers/ or $trigger_file is not a file." - fi -done - -# Start the application -echo "Starting the application..." -exec "$@" diff --git a/web/eslint.config.js b/web/eslint.config.js deleted file mode 100644 index 82b9319..0000000 --- a/web/eslint.config.js +++ /dev/null @@ -1,147 +0,0 @@ -// @ts-check -import pluginJs from '@eslint/js' -import stylistic from '@stylistic/eslint-plugin' -import { configs as eslintAstroPluginConfig } from 'eslint-plugin-astro' -import importPlugin from 'eslint-plugin-import' -import globals from 'globals' -import { without } from 'lodash-es' -import tseslint, { configs as tseslintConfigs } from 'typescript-eslint' - -export default tseslint.config( - { - ignores: [ - '**/node_modules/**', - '.astro/**', - 'dist/**', - 'coverage/**', - 'build/**', - 'public/**', - '.prettierrc.mjs', - ], - }, - { - files: ['**/*.{js,ts,mjs,cjs,tsx,jsx,astro}'], - }, - { - settings: { - 'import/resolver': { - typescript: { - alwaysTryTypes: true, - project: 'tsconfig.json', - }, - }, - }, - }, - pluginJs.configs.recommended, - tseslintConfigs.strictTypeChecked, - tseslintConfigs.stylisticTypeChecked, - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - importPlugin.flatConfigs.recommended, - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - importPlugin.flatConfigs.typescript, - eslintAstroPluginConfig['flat/recommended'], - eslintAstroPluginConfig['flat/jsx-a11y-strict'], - [ - // These rules don't work with Astro and produce false positives - { - files: ['**/*.astro'], - rules: { - '@typescript-eslint/no-misused-promises': 'off', - '@typescript-eslint/no-unsafe-return': 'off', - '@typescript-eslint/no-redundant-type-constituents': 'off', - '@typescript-eslint/no-unsafe-member-access': 'off', - '@typescript-eslint/no-unsafe-call': 'off', - '@typescript-eslint/no-unsafe-assignment': 'off', - '@typescript-eslint/no-unsafe-argument': 'off', - '@typescript-eslint/restrict-template-expressions': 'off', - }, - }, - { - rules: { - '@typescript-eslint/no-unsafe-assignment': 'off', - }, - }, - ], - { - languageOptions: { - globals: { - ...globals.browser, - ...globals.node, - }, - parserOptions: { - project: true, - tsconfigRootDir: import.meta.dirname, - }, - }, - plugins: { - '@stylistic': stylistic, - }, - rules: { - '@typescript-eslint/unbound-method': 'off', - '@typescript-eslint/no-unnecessary-type-parameters': 'off', - '@typescript-eslint/no-deprecated': 'warn', - '@typescript-eslint/prefer-nullish-coalescing': 'warn', - '@typescript-eslint/consistent-type-definitions': ['warn', 'type'], - '@typescript-eslint/no-unused-vars': [ - 'warn', - { - args: 'all', - argsIgnorePattern: '^_', - caughtErrors: 'all', - caughtErrorsIgnorePattern: '^_', - destructuredArrayIgnorePattern: '^_', - varsIgnorePattern: '^_', - ignoreRestSiblings: true, - }, - ], - '@typescript-eslint/consistent-type-imports': [ - 'error', - { prefer: 'type-imports', fixStyle: 'separate-type-imports' }, - ], - '@typescript-eslint/sort-type-constituents': 'error', - 'import/order': [ - 'warn', - { - groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'], - pathGroups: [ - { - pattern: 'react', - group: 'external', - position: 'before', - }, - ], - pathGroupsExcludedImportTypes: ['react'], - 'newlines-between': 'always', - alphabetize: { - order: 'asc', - caseInsensitive: true, - }, - }, - ], - 'import/first': 'error', - 'import/newline-after-import': 'error', - 'import/no-duplicates': 'error', - 'import/no-unresolved': ['error', { ignore: ['^astro:'] }], - '@typescript-eslint/no-explicit-any': 'warn', - 'no-console': ['warn', { allow: without(Object.keys(console), 'log') }], - 'import/namespace': 'off', - 'object-shorthand': ['warn', 'always', { avoidExplicitReturnArrows: false }], - 'no-useless-rename': 'warn', - curly: ['error', 'multi-line'], - '@stylistic/quotes': [ - 'error', - 'single', - { - avoidEscape: true, - allowTemplateLiterals: false, - }, - ], - }, - }, - { - files: ['**/*.d.ts'], - rules: { - '@typescript-eslint/no-explicit-any': 'off', - }, - } -) diff --git a/web/package-lock.json b/web/package-lock.json deleted file mode 100644 index dcf928a..0000000 --- a/web/package-lock.json +++ /dev/null @@ -1,14561 +0,0 @@ -{ - "name": "kycnot.me", - "version": "0.0.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "kycnot.me", - "version": "0.0.1", - "dependencies": { - "@astrojs/check": "0.9.4", - "@astrojs/db": "0.14.14", - "@astrojs/mdx": "4.2.6", - "@astrojs/node": "9.2.1", - "@astrojs/sitemap": "3.4.0", - "@fontsource-variable/space-grotesk": "5.2.7", - "@fontsource/inter": "5.2.5", - "@prisma/client": "6.8.2", - "@tailwindcss/vite": "4.1.7", - "@types/mime-types": "2.1.4", - "@vercel/og": "0.6.8", - "astro": "5.7.13", - "astro-loading-indicator": "0.7.0", - "astro-remote": "0.3.4", - "astro-seo-schema": "5.0.0", - "canvas": "3.1.0", - "clsx": "2.1.1", - "htmx.org": "1.9.12", - "javascript-time-ago": "2.5.11", - "libphonenumber-js": "1.12.8", - "lodash-es": "4.17.21", - "mime-types": "3.0.1", - "object-to-formdata": "4.5.1", - "react": "19.1.0", - "redis": "5.0.1", - "schema-dts": "1.1.5", - "seedrandom": "3.0.5", - "slugify": "1.6.6", - "tailwind-merge": "3.3.0", - "tailwind-variants": "1.0.0", - "tailwindcss": "4.1.7", - "typescript": "5.8.3", - "unique-username-generator": "1.4.0", - "zod-form-data": "2.0.7" - }, - "devDependencies": { - "@eslint/js": "9.27.0", - "@faker-js/faker": "9.8.0", - "@iconify-json/material-symbols": "1.2.21", - "@iconify-json/mdi": "1.2.3", - "@iconify-json/ri": "1.2.5", - "@stylistic/eslint-plugin": "4.2.0", - "@tailwindcss/forms": "0.5.10", - "@tailwindcss/typography": "0.5.16", - "@types/eslint__js": "9.14.0", - "@types/lodash-es": "4.17.12", - "@types/react": "19.1.4", - "@types/seedrandom": "3.0.8", - "@typescript-eslint/parser": "8.32.1", - "astro-icon": "1.1.5", - "date-fns": "4.1.0", - "eslint": "9.27.0", - "eslint-import-resolver-typescript": "4.3.5", - "eslint-plugin-astro": "1.3.1", - "eslint-plugin-import": "2.31.0", - "eslint-plugin-jsx-a11y": "6.10.2", - "globals": "16.1.0", - "prettier": "3.5.3", - "prettier-plugin-astro": "0.14.1", - "prettier-plugin-tailwindcss": "0.6.11", - "prisma": "6.8.2", - "prisma-json-types-generator": "3.4.1", - "tailwind-htmx": "0.1.2", - "ts-essentials": "10.0.4", - "ts-toolbelt": "9.6.0", - "tsx": "4.19.4", - "typescript-eslint": "8.32.1" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@antfu/install-pkg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.0.0.tgz", - "integrity": "sha512-xvX6P/lo1B3ej0OsaErAjqgFYzYVcJpamjLAFLYh9vRJngBrMoUG7aVnrGTeqM7yxbyTD5p3F2+0/QUEh8Vzhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "package-manager-detector": "^0.2.8", - "tinyexec": "^0.3.2" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@antfu/utils": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-8.1.0.tgz", - "integrity": "sha512-XPR7Jfwp0FFl/dFYPX8ZjpmU4/1mIXTjnZ1ba48BLMyKOV62/tiRjdsFcPs2hsYcSud4tzk7w3a3LjX8Fu3huA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@astrojs/check": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/@astrojs/check/-/check-0.9.4.tgz", - "integrity": "sha512-IOheHwCtpUfvogHHsvu0AbeRZEnjJg3MopdLddkJE70mULItS/Vh37BHcI00mcOJcH1vhD3odbpvWokpxam7xA==", - "license": "MIT", - "dependencies": { - "@astrojs/language-server": "^2.15.0", - "chokidar": "^4.0.1", - "kleur": "^4.1.5", - "yargs": "^17.7.2" - }, - "bin": { - "astro-check": "dist/bin.js" - }, - "peerDependencies": { - "typescript": "^5.0.0" - } - }, - "node_modules/@astrojs/compiler": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.11.0.tgz", - "integrity": "sha512-zZOO7i+JhojO8qmlyR/URui6LyfHJY6m+L9nwyX5GiKD78YoRaZ5tzz6X0fkl+5bD3uwlDHayf6Oe8Fu36RKNg==", - "license": "MIT" - }, - "node_modules/@astrojs/db": { - "version": "0.14.14", - "resolved": "https://registry.npmjs.org/@astrojs/db/-/db-0.14.14.tgz", - "integrity": "sha512-NSLa7P+MGv9icVWmvV2KVzjUZEWUYQ3q5W5XNTghhrxRGkFkNtAHs+xPBDt+VGhcYtRedzqWXRhHooLLJkKb9g==", - "license": "MIT", - "dependencies": { - "@astrojs/studio": "0.1.9", - "@libsql/client": "^0.15.2", - "async-listen": "^3.1.0", - "deep-diff": "^1.0.2", - "drizzle-orm": "^0.31.2", - "github-slugger": "^2.0.0", - "kleur": "^4.1.5", - "nanoid": "^5.1.5", - "open": "^10.1.0", - "prompts": "^2.4.2", - "yargs-parser": "^21.1.1", - "yocto-spinner": "^0.2.1", - "zod": "^3.24.2" - } - }, - "node_modules/@astrojs/db/node_modules/nanoid": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", - "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^18 || >=20" - } - }, - "node_modules/@astrojs/internal-helpers": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.6.1.tgz", - "integrity": "sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A==", - "license": "MIT" - }, - "node_modules/@astrojs/language-server": { - "version": "2.15.4", - "resolved": "https://registry.npmjs.org/@astrojs/language-server/-/language-server-2.15.4.tgz", - "integrity": "sha512-JivzASqTPR2bao9BWsSc/woPHH7OGSGc9aMxXL4U6egVTqBycB3ZHdBJPuOCVtcGLrzdWTosAqVPz1BVoxE0+A==", - "license": "MIT", - "dependencies": { - "@astrojs/compiler": "^2.10.3", - "@astrojs/yaml2ts": "^0.2.2", - "@jridgewell/sourcemap-codec": "^1.4.15", - "@volar/kit": "~2.4.7", - "@volar/language-core": "~2.4.7", - "@volar/language-server": "~2.4.7", - "@volar/language-service": "~2.4.7", - "fast-glob": "^3.2.12", - "muggle-string": "^0.4.1", - "volar-service-css": "0.0.62", - "volar-service-emmet": "0.0.62", - "volar-service-html": "0.0.62", - "volar-service-prettier": "0.0.62", - "volar-service-typescript": "0.0.62", - "volar-service-typescript-twoslash-queries": "0.0.62", - "volar-service-yaml": "0.0.62", - "vscode-html-languageservice": "^5.2.0", - "vscode-uri": "^3.0.8" - }, - "bin": { - "astro-ls": "bin/nodeServer.js" - }, - "peerDependencies": { - "prettier": "^3.0.0", - "prettier-plugin-astro": ">=0.11.0" - }, - "peerDependenciesMeta": { - "prettier": { - "optional": true - }, - "prettier-plugin-astro": { - "optional": true - } - } - }, - "node_modules/@astrojs/markdown-remark": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-6.3.1.tgz", - "integrity": "sha512-c5F5gGrkczUaTVgmMW9g1YMJGzOtRvjjhw6IfGuxarM6ct09MpwysP10US729dy07gg8y+ofVifezvP3BNsWZg==", - "license": "MIT", - "dependencies": { - "@astrojs/internal-helpers": "0.6.1", - "@astrojs/prism": "3.2.0", - "github-slugger": "^2.0.0", - "hast-util-from-html": "^2.0.3", - "hast-util-to-text": "^4.0.2", - "import-meta-resolve": "^4.1.0", - "js-yaml": "^4.1.0", - "mdast-util-definitions": "^6.0.0", - "rehype-raw": "^7.0.0", - "rehype-stringify": "^10.0.1", - "remark-gfm": "^4.0.1", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.1.1", - "remark-smartypants": "^3.0.2", - "shiki": "^3.0.0", - "smol-toml": "^1.3.1", - "unified": "^11.0.5", - "unist-util-remove-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "unist-util-visit-parents": "^6.0.1", - "vfile": "^6.0.3" - } - }, - "node_modules/@astrojs/mdx": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-4.2.6.tgz", - "integrity": "sha512-0i/GmOm6d0qq1/SCfcUgY/IjDc/bS0i42u7h85TkPFBmlFOcBZfkYhR5iyz6hZLwidvJOEq5yGfzt9B1Azku4w==", - "license": "MIT", - "dependencies": { - "@astrojs/markdown-remark": "6.3.1", - "@mdx-js/mdx": "^3.1.0", - "acorn": "^8.14.1", - "es-module-lexer": "^1.6.0", - "estree-util-visit": "^2.0.0", - "hast-util-to-html": "^9.0.5", - "kleur": "^4.1.5", - "rehype-raw": "^7.0.0", - "remark-gfm": "^4.0.1", - "remark-smartypants": "^3.0.2", - "source-map": "^0.7.4", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.3" - }, - "engines": { - "node": "^18.17.1 || ^20.3.0 || >=22.0.0" - }, - "peerDependencies": { - "astro": "^5.0.0" - } - }, - "node_modules/@astrojs/node": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@astrojs/node/-/node-9.2.1.tgz", - "integrity": "sha512-kEHLB37ooW91p7FLGalqa3jVQRIafntfKiZgCnjN1lEYw+j8NP6VJHQbLHmzzbtKUI0J+srGiTnGZmaHErHE5w==", - "license": "MIT", - "dependencies": { - "@astrojs/internal-helpers": "0.6.1", - "send": "^1.1.0", - "server-destroy": "^1.0.1" - }, - "peerDependencies": { - "astro": "^5.3.0" - } - }, - "node_modules/@astrojs/prism": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.2.0.tgz", - "integrity": "sha512-GilTHKGCW6HMq7y3BUv9Ac7GMe/MO9gi9GW62GzKtth0SwukCu/qp2wLiGpEujhY+VVhaG9v7kv/5vFzvf4NYw==", - "license": "MIT", - "dependencies": { - "prismjs": "^1.29.0" - }, - "engines": { - "node": "^18.17.1 || ^20.3.0 || >=22.0.0" - } - }, - "node_modules/@astrojs/sitemap": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.4.0.tgz", - "integrity": "sha512-C5m/xsKvRSILKM3hy47n5wKtTQtJXn8epoYuUmCCstaE9XBt20yInym3Bz2uNbEiNfv11bokoW0MqeXPIvjFIQ==", - "license": "MIT", - "dependencies": { - "sitemap": "^8.0.0", - "stream-replace-string": "^2.0.0", - "zod": "^3.24.2" - } - }, - "node_modules/@astrojs/studio": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/@astrojs/studio/-/studio-0.1.9.tgz", - "integrity": "sha512-pMZ+gYVC2XAnHfzp/or9tyEor8yKJyTUflm6oQJAad5+4z0d/033mhJVCU4kKDXXKovAGB4bcHgyW97ixCE6fQ==", - "license": "MIT", - "dependencies": { - "ci-info": "^4.2.0", - "kleur": "^4.1.5", - "yocto-spinner": "^0.2.1" - } - }, - "node_modules/@astrojs/telemetry": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.2.1.tgz", - "integrity": "sha512-SSVM820Jqc6wjsn7qYfV9qfeQvePtVc1nSofhyap7l0/iakUKywj3hfy3UJAOV4sGV4Q/u450RD4AaCaFvNPlg==", - "license": "MIT", - "dependencies": { - "ci-info": "^4.2.0", - "debug": "^4.4.0", - "dlv": "^1.1.3", - "dset": "^3.1.4", - "is-docker": "^3.0.0", - "is-wsl": "^3.1.0", - "which-pm-runs": "^1.1.0" - }, - "engines": { - "node": "^18.17.1 || ^20.3.0 || >=22.0.0" - } - }, - "node_modules/@astrojs/yaml2ts": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@astrojs/yaml2ts/-/yaml2ts-0.2.2.tgz", - "integrity": "sha512-GOfvSr5Nqy2z5XiwqTouBBpy5FyI6DEe+/g/Mk5am9SjILN1S5fOEvYK0GuWHg98yS/dobP4m8qyqw/URW35fQ==", - "license": "MIT", - "dependencies": { - "yaml": "^2.5.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", - "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/types": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", - "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@capsizecss/unpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-2.4.0.tgz", - "integrity": "sha512-GrSU71meACqcmIUxPYOJvGKF0yryjN/L1aCuE9DViCTJI7bfkjgYDPD1zbNDcINJwSSP6UaBZY9GAbYDO7re0Q==", - "license": "MIT", - "dependencies": { - "blob-to-buffer": "^1.2.8", - "cross-fetch": "^3.0.4", - "fontkit": "^2.0.2" - } - }, - "node_modules/@emmetio/abbreviation": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz", - "integrity": "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==", - "license": "MIT", - "dependencies": { - "@emmetio/scanner": "^1.0.4" - } - }, - "node_modules/@emmetio/css-abbreviation": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@emmetio/css-abbreviation/-/css-abbreviation-2.1.8.tgz", - "integrity": "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==", - "license": "MIT", - "dependencies": { - "@emmetio/scanner": "^1.0.4" - } - }, - "node_modules/@emmetio/css-parser": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@emmetio/css-parser/-/css-parser-0.4.0.tgz", - "integrity": "sha512-z7wkxRSZgrQHXVzObGkXG+Vmj3uRlpM11oCZ9pbaz0nFejvCDmAiNDpY75+wgXOcffKpj4rzGtwGaZxfJKsJxw==", - "license": "MIT", - "dependencies": { - "@emmetio/stream-reader": "^2.2.0", - "@emmetio/stream-reader-utils": "^0.1.0" - } - }, - "node_modules/@emmetio/html-matcher": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@emmetio/html-matcher/-/html-matcher-1.3.0.tgz", - "integrity": "sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==", - "license": "ISC", - "dependencies": { - "@emmetio/scanner": "^1.0.0" - } - }, - "node_modules/@emmetio/scanner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@emmetio/scanner/-/scanner-1.0.4.tgz", - "integrity": "sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==", - "license": "MIT" - }, - "node_modules/@emmetio/stream-reader": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@emmetio/stream-reader/-/stream-reader-2.2.0.tgz", - "integrity": "sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==", - "license": "MIT" - }, - "node_modules/@emmetio/stream-reader-utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@emmetio/stream-reader-utils/-/stream-reader-utils-0.1.0.tgz", - "integrity": "sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==", - "license": "MIT" - }, - "node_modules/@emnapi/core": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", - "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==", - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.0.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.0.tgz", - "integrity": "sha512-64WYIf4UYcdLnbKn/umDlNjQDSS8AgZrI/R9+x5ilkUVFxXcA1Ebl+gQLc/6mERA4407Xof0R7wEyEuj091CVw==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz", - "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", - "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", - "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", - "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", - "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", - "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", - "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", - "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", - "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", - "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", - "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", - "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", - "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", - "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", - "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", - "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", - "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", - "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", - "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", - "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", - "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", - "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", - "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", - "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", - "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", - "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.6", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz", - "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", - "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/js": { - "version": "9.27.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", - "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", - "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.14.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@faker-js/faker": { - "version": "9.8.0", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.8.0.tgz", - "integrity": "sha512-U9wpuSrJC93jZBxx/Qq2wPjCuYISBueyVUGK7qqdmj7r/nxaxwW8AQDCLeRO7wZnjj94sh3p246cAYjUKuqgfg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/fakerjs" - } - ], - "license": "MIT", - "engines": { - "node": ">=18.0.0", - "npm": ">=9.0.0" - } - }, - "node_modules/@fontsource-variable/space-grotesk": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/@fontsource-variable/space-grotesk/-/space-grotesk-5.2.7.tgz", - "integrity": "sha512-/f75B8XXMJgqg5AuboxR7Utkd/GnzZhP5gY0FDdx0M8/CoM8NzH1Jn6bm3gX44Gb+TACI/wiXPgTpph28ipi9g==", - "license": "OFL-1.1", - "funding": { - "url": "https://github.com/sponsors/ayuhito" - } - }, - "node_modules/@fontsource/inter": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-5.2.5.tgz", - "integrity": "sha512-kbsPKj0S4p44JdYRFiW78Td8Ge2sBVxi/PIBwmih+RpSXUdvS9nbs1HIiuUSPtRMi14CqLEZ/fbk7dj7vni1Sg==", - "license": "OFL-1.1", - "funding": { - "url": "https://github.com/sponsors/ayuhito" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", - "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@iconify-json/material-symbols": { - "version": "1.2.21", - "resolved": "https://registry.npmjs.org/@iconify-json/material-symbols/-/material-symbols-1.2.21.tgz", - "integrity": "sha512-78o99N3oYeWY91EvZDl5bAPSPtO3rgkPwuOfgk4iBLbpb2peE6ueZFiMA/RURpy8jf4Pyt9rUtjP34UJIkVAcA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@iconify/types": "*" - } - }, - "node_modules/@iconify-json/mdi": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@iconify-json/mdi/-/mdi-1.2.3.tgz", - "integrity": "sha512-O3cLwbDOK7NNDf2ihaQOH5F9JglnulNDFV7WprU2dSoZu3h3cWH//h74uQAB87brHmvFVxIOkuBX2sZSzYhScg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@iconify/types": "*" - } - }, - "node_modules/@iconify-json/ri": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@iconify-json/ri/-/ri-1.2.5.tgz", - "integrity": "sha512-kWGimOXMZrlYusjBKKXYOWcKhbOHusFsmrmRGmjS7rH0BpML5A9/fy8KHZqFOwZfC4M6amObQYbh8BqO5cMC3w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@iconify/types": "*" - } - }, - "node_modules/@iconify/tools": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@iconify/tools/-/tools-4.1.1.tgz", - "integrity": "sha512-Hybu/HGhv6T8nLQhiG9rKx+ekF7NNpPOEQAy7JRSKht3s3dcFSsPccYzk24Znc9MTxrR6Gak3cg6CPP5dyvS2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@iconify/types": "^2.0.0", - "@iconify/utils": "^2.2.0", - "@types/tar": "^6.1.13", - "axios": "^1.7.9", - "cheerio": "1.0.0", - "domhandler": "^5.0.3", - "extract-zip": "^2.0.1", - "local-pkg": "^0.5.1", - "pathe": "^1.1.2", - "svgo": "^3.3.2", - "tar": "^6.2.1" - } - }, - "node_modules/@iconify/types": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", - "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@iconify/utils": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.3.0.tgz", - "integrity": "sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@antfu/install-pkg": "^1.0.0", - "@antfu/utils": "^8.1.0", - "@iconify/types": "^2.0.0", - "debug": "^4.4.0", - "globals": "^15.14.0", - "kolorist": "^1.8.0", - "local-pkg": "^1.0.0", - "mlly": "^1.7.4" - } - }, - "node_modules/@iconify/utils/node_modules/globals": { - "version": "15.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", - "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@iconify/utils/node_modules/local-pkg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.0.0.tgz", - "integrity": "sha512-bbgPw/wmroJsil/GgL4qjDzs5YLTBMQ99weRsok1XCDccQeehbHA/I1oRvk2NPtr7KGZgT/Y5tPRnAtMqeG2Kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mlly": "^1.7.3", - "pkg-types": "^1.3.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", - "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", - "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", - "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", - "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", - "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", - "cpu": [ - "arm" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", - "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", - "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", - "cpu": [ - "s390x" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", - "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", - "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", - "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", - "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", - "cpu": [ - "arm" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.0.5" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", - "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", - "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", - "cpu": [ - "s390x" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.0.4" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", - "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", - "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", - "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", - "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", - "cpu": [ - "wasm32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.2.0" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", - "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", - "cpu": [ - "ia32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", - "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@isaacs/fs-minipass/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@libsql/client": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.15.2.tgz", - "integrity": "sha512-D0No4jqDj5I+buvEyFajBugohzJXCBt9aRHCEXGrJS/9obnAO2z18Os3xgyPsWX0Yw4NQfSYaayRdowqkssmXA==", - "license": "MIT", - "dependencies": { - "@libsql/core": "^0.15.2", - "@libsql/hrana-client": "^0.7.0", - "js-base64": "^3.7.5", - "libsql": "^0.5.4", - "promise-limit": "^2.7.0" - } - }, - "node_modules/@libsql/core": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@libsql/core/-/core-0.15.2.tgz", - "integrity": "sha512-+UIN0OlzWa54MqnHbtaJ3FEJj6k2VrwrjX1sSSxzYlM+dWuadjMwOVp7gHpSYJGKWw0RQWLGge4fbW4TCvIm3A==", - "license": "MIT", - "dependencies": { - "js-base64": "^3.7.5" - } - }, - "node_modules/@libsql/darwin-arm64": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.5.4.tgz", - "integrity": "sha512-4PnRdklaQg27vAZxtQgKl+xBHimCH2KRgKId+h63gkAtz5yFTMmX+Q4Ez804T1BgrZuB5ujIvueEEuust2ceSQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@libsql/darwin-x64": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.5.4.tgz", - "integrity": "sha512-r+Z3UXQWxluXKA5cPj5KciNsmSXVTnq9/tmDczngJrogyXwdbbSShYkzov5M+YBlUCKv2VCbNnfxxoIqQnV9Gg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@libsql/hrana-client": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@libsql/hrana-client/-/hrana-client-0.7.0.tgz", - "integrity": "sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw==", - "license": "MIT", - "dependencies": { - "@libsql/isomorphic-fetch": "^0.3.1", - "@libsql/isomorphic-ws": "^0.1.5", - "js-base64": "^3.7.5", - "node-fetch": "^3.3.2" - } - }, - "node_modules/@libsql/isomorphic-fetch": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@libsql/isomorphic-fetch/-/isomorphic-fetch-0.3.1.tgz", - "integrity": "sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@libsql/isomorphic-ws": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@libsql/isomorphic-ws/-/isomorphic-ws-0.1.5.tgz", - "integrity": "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==", - "license": "MIT", - "dependencies": { - "@types/ws": "^8.5.4", - "ws": "^8.13.0" - } - }, - "node_modules/@libsql/linux-arm64-gnu": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.5.4.tgz", - "integrity": "sha512-QmGXa3TGM6URe7vCOqdvr4Koay+4h5D6y4gdhnPCvXNYrRHgpq5OwEafP9GFalbO32Y1ppLY4enO2LwY0k63Qw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@libsql/linux-arm64-musl": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.5.4.tgz", - "integrity": "sha512-cx4/7/xUjgNbiRsghRHujSvIqaTNFQC7Oo1gkGXGsh8hBwkdXr1QdOpeitq745sl6OlbInRrW2C7B2juxX3hcQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@libsql/linux-x64-gnu": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.5.4.tgz", - "integrity": "sha512-oPrE9Zyqd7fElS9uCGW2jn55cautD+gDIflfyF5+W/QYzll5OJ2vyMBZOBgdNopuZHrmHYihbespJn3t0WJDJg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@libsql/linux-x64-musl": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.5.4.tgz", - "integrity": "sha512-XzyVdVe43MexkAaHzUvsi4tpPhfSDn3UndIYFrIu0lYkkiz4oKjTK7Iq96j2bcOeJv0pBGxiv+8Z9I6yp/aI2A==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@libsql/win32-x64-msvc": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.5.4.tgz", - "integrity": "sha512-xWQyAQEsX+odBrMSXTpm3WOFeoJIX7QncCkaZcsaqdEFueOdNDIdcKAQKMoNlwtj1rCxE72RK4byw/Bflf6Jgg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@mdx-js/mdx": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.0.tgz", - "integrity": "sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdx": "^2.0.0", - "collapse-white-space": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "estree-util-scope": "^1.0.0", - "estree-walker": "^3.0.0", - "hast-util-to-jsx-runtime": "^2.0.0", - "markdown-extensions": "^2.0.0", - "recma-build-jsx": "^1.0.0", - "recma-jsx": "^1.0.0", - "recma-stringify": "^1.0.0", - "rehype-recma": "^1.0.0", - "remark-mdx": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.0.0", - "source-map": "^0.7.0", - "unified": "^11.0.0", - "unist-util-position-from-estree": "^2.0.0", - "unist-util-stringify-position": "^4.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.9.tgz", - "integrity": "sha512-OKRBiajrrxB9ATokgEQoG87Z25c67pCpYcCwmXYX8PBftC9pBfN18gnm/fh1wurSLEKIAt+QRFLFCQISrb66Jg==", - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.0", - "@emnapi/runtime": "^1.4.0", - "@tybys/wasm-util": "^0.9.0" - } - }, - "node_modules/@neon-rs/load": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", - "integrity": "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==", - "license": "MIT" - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@oslojs/encoding": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", - "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", - "license": "MIT" - }, - "node_modules/@pkgr/core": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", - "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, - "node_modules/@prisma/client": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.8.2.tgz", - "integrity": "sha512-5II+vbyzv4si6Yunwgkj0qT/iY0zyspttoDrL3R4BYgLdp42/d2C8xdi9vqkrYtKt9H32oFIukvyw3Koz5JoDg==", - "hasInstallScript": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "peerDependencies": { - "prisma": "*", - "typescript": ">=5.1.0" - }, - "peerDependenciesMeta": { - "prisma": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/@prisma/config": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.8.2.tgz", - "integrity": "sha512-ZJY1fF4qRBPdLQ/60wxNtX+eu89c3AkYEcP7L3jkp0IPXCNphCYxikTg55kPJLDOG6P0X+QG5tCv6CmsBRZWFQ==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "jiti": "2.4.2" - } - }, - "node_modules/@prisma/debug": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.7.0.tgz", - "integrity": "sha512-RabHn9emKoYFsv99RLxvfG2GHzWk2ZI1BuVzqYtmMSIcuGboHY5uFt3Q3boOREM9de6z5s3bQoyKeWnq8Fz22w==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/dmmf": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@prisma/dmmf/-/dmmf-6.7.0.tgz", - "integrity": "sha512-Nabzf5H7KUwYV/1u8R5gTQ7x3ffdPIhtxVJsjhnLOjeuem8YzVu39jOgQbiOlfOvjU4sPYkV9wEXc5tIHumdUw==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/engines": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.8.2.tgz", - "integrity": "sha512-XqAJ//LXjqYRQ1RRabs79KOY4+v6gZOGzbcwDQl0D6n9WBKjV7qdrbd042CwSK0v0lM9MSHsbcFnU2Yn7z8Zlw==", - "devOptional": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "6.8.2", - "@prisma/engines-version": "6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e", - "@prisma/fetch-engine": "6.8.2", - "@prisma/get-platform": "6.8.2" - } - }, - "node_modules/@prisma/engines-version": { - "version": "6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e.tgz", - "integrity": "sha512-Rkik9lMyHpFNGaLpPF3H5q5TQTkm/aE7DsGM5m92FZTvWQsvmi6Va8On3pWvqLHOt5aPUvFb/FeZTmphI4CPiQ==", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/engines/node_modules/@prisma/debug": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.8.2.tgz", - "integrity": "sha512-4muBSSUwJJ9BYth5N8tqts8JtiLT8QI/RSAzEogwEfpbYGFo9mYsInsVo8dqXdPO2+Rm5OG5q0qWDDE3nyUbVg==", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/fetch-engine": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.8.2.tgz", - "integrity": "sha512-lCvikWOgaLOfqXGacEKSNeenvj0n3qR5QvZUOmPE2e1Eh8cMYSobxonCg9rqM6FSdTfbpqp9xwhSAOYfNqSW0g==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "6.8.2", - "@prisma/engines-version": "6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e", - "@prisma/get-platform": "6.8.2" - } - }, - "node_modules/@prisma/fetch-engine/node_modules/@prisma/debug": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.8.2.tgz", - "integrity": "sha512-4muBSSUwJJ9BYth5N8tqts8JtiLT8QI/RSAzEogwEfpbYGFo9mYsInsVo8dqXdPO2+Rm5OG5q0qWDDE3nyUbVg==", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/generator": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@prisma/generator/-/generator-6.7.0.tgz", - "integrity": "sha512-wCsD7QJn1JBKJfv5uOMhwBCvZPGerPJ3EC2HocFPFaHSzVIXLi/zNb/8gpBxervOXTbQRJ2dpqvMsJnwAnPi8A==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/generator-helper": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@prisma/generator-helper/-/generator-helper-6.7.0.tgz", - "integrity": "sha512-z4ey7n8eUFx7Ol7RJCoEo9SVK2roy80qLXdrUFlmswcs0e/z03wDl4tpaA02BE2Yi9KCZl2Tkd6oMes81vUefA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "6.7.0", - "@prisma/dmmf": "6.7.0", - "@prisma/generator": "6.7.0" - } - }, - "node_modules/@prisma/get-platform": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.8.2.tgz", - "integrity": "sha512-vXSxyUgX3vm1Q70QwzwkjeYfRryIvKno1SXbIqwSptKwqKzskINnDUcx85oX+ys6ooN2ATGSD0xN2UTfg6Zcow==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "6.8.2" - } - }, - "node_modules/@prisma/get-platform/node_modules/@prisma/debug": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.8.2.tgz", - "integrity": "sha512-4muBSSUwJJ9BYth5N8tqts8JtiLT8QI/RSAzEogwEfpbYGFo9mYsInsVo8dqXdPO2+Rm5OG5q0qWDDE3nyUbVg==", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@redis/bloom": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.0.1.tgz", - "integrity": "sha512-F7L+rnuJvq/upKaVoEgsf8VT7g5pLQYWRqSUOV3uO4vpVtARzSKJ7CLyJjVsQS+wZVCGxsLMh8DwAIDcny1B+g==", - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@redis/client": "^5.0.1" - } - }, - "node_modules/@redis/client": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.0.1.tgz", - "integrity": "sha512-k0EJvlMGEyBqUD3orKe0UMZ66fPtfwqPIr+ZSd853sXj2EyhNtPXSx+J6sENXJNgAlEBhvD+57Dwt0qTisKB0A==", - "license": "MIT", - "dependencies": { - "cluster-key-slot": "1.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@redis/json": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@redis/json/-/json-5.0.1.tgz", - "integrity": "sha512-t94HOTk5myfhvaHZzlUzk2hoUvH2jsjftcnMgJWuHL/pzjAJQoZDCUJzjkoXIUjWXuyJixTguaaDyOZWwqH2Kg==", - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@redis/client": "^5.0.1" - } - }, - "node_modules/@redis/search": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-5.0.1.tgz", - "integrity": "sha512-wipK6ZptY7K68B7YLVhP5I/wYCDUU+mDJMyJiUcQLuOs7/eKOBc8lTXKUSssor8QnzZSPy4A5ulcC5PZY22Zgw==", - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@redis/client": "^5.0.1" - } - }, - "node_modules/@redis/time-series": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.0.1.tgz", - "integrity": "sha512-k6PgbrakhnohsEWEAdQZYt3e5vSKoIzpKvgQt8//lnWLrTZx+c3ed2sj0+pKIF4FvnSeuXLo4bBWcH0Z7Urg1A==", - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@redis/client": "^5.0.1" - } - }, - "node_modules/@resvg/resvg-wasm": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@resvg/resvg-wasm/-/resvg-wasm-2.4.0.tgz", - "integrity": "sha512-C7c51Nn4yTxXFKvgh2txJFNweaVcfUPQxwEUFw4aWsCmfiBDJsTSwviIF8EcwjQ6k8bPyMWCl1vw4BdxE569Cg==", - "license": "MPL-2.0", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@rollup/pluginutils": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", - "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.1.tgz", - "integrity": "sha512-kxz0YeeCrRUHz3zyqvd7n+TVRlNyTifBsmnmNPtk3hQURUyG9eAB+usz6DAwagMusjx/zb3AjvDUvhFGDAexGw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.1.tgz", - "integrity": "sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.1.tgz", - "integrity": "sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.1.tgz", - "integrity": "sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.1.tgz", - "integrity": "sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.1.tgz", - "integrity": "sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.1.tgz", - "integrity": "sha512-ehSKrewwsESPt1TgSE/na9nIhWCosfGSFqv7vwEtjyAqZcvbGIg4JAcV7ZEh2tfj/IlfBeZjgOXm35iOOjadcg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.1.tgz", - "integrity": "sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.1.tgz", - "integrity": "sha512-Y+GHnGaku4aVLSgrT0uWe2o2Rq8te9hi+MwqGF9r9ORgXhmHK5Q71N757u0F8yU1OIwUIFy6YiJtKjtyktk5hg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.1.tgz", - "integrity": "sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.1.tgz", - "integrity": "sha512-ySyWikVhNzv+BV/IDCsrraOAZ3UaC8SZB67FZlqVwXwnFhPihOso9rPOxzZbjp81suB1O2Topw+6Ug3JNegejQ==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.1.tgz", - "integrity": "sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.1.tgz", - "integrity": "sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.1.tgz", - "integrity": "sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.1.tgz", - "integrity": "sha512-h8d28xzYb98fMQKUz0w2fMc1XuGzLLjdyxVIbhbil4ELfk5/orZlSTpF/xdI9C8K0I8lCkq+1En2RJsawZekkg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.1.tgz", - "integrity": "sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.1.tgz", - "integrity": "sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.1.tgz", - "integrity": "sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.1.tgz", - "integrity": "sha512-DfcogW8N7Zg7llVEfpqWMZcaErKfsj9VvmfSyRjCyo4BI3wPEfrzTtJkZG6gKP/Z92wFm6rz2aDO7/JfiR/whA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.1.tgz", - "integrity": "sha512-ECyOuDeH3C1I8jH2MK1RtBJW+YPMvSfT0a5NN0nHfQYnDSJ6tUiZH3gzwVP5/Kfh/+Tt7tpWVF9LXNTnhTJ3kA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rvf/set-get": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@rvf/set-get/-/set-get-7.0.1.tgz", - "integrity": "sha512-GkTSn9K1GrTYoTUqlUs36k6nJnzjQaFBTTEIqUYmzBcsGsoJM8xG7EAx2WLHWAA4QzFjcwWUSHQ3vM3Fbw50Tg==", - "license": "MIT" - }, - "node_modules/@shikijs/core": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.2.1.tgz", - "integrity": "sha512-FhsdxMWYu/C11sFisEp7FMGBtX/OSSbnXZDMBhGuUDBNTdsoZlMSgQv5f90rwvzWAdWIW6VobD+G3IrazxA6dQ==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.2.1", - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4", - "hast-util-to-html": "^9.0.5" - } - }, - "node_modules/@shikijs/engine-javascript": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.2.1.tgz", - "integrity": "sha512-eMdcUzN3FMQYxOmRf2rmU8frikzoSHbQDFH2hIuXsrMO+IBOCI9BeeRkCiBkcLDHeRKbOCtYMJK3D6U32ooU9Q==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.2.1", - "@shikijs/vscode-textmate": "^10.0.2", - "oniguruma-to-es": "^4.1.0" - } - }, - "node_modules/@shikijs/engine-oniguruma": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.2.1.tgz", - "integrity": "sha512-wZZAkayEn6qu2+YjenEoFqj0OyQI64EWsNR6/71d1EkG4sxEOFooowKivsWPpaWNBu3sxAG+zPz5kzBL/SsreQ==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.2.1", - "@shikijs/vscode-textmate": "^10.0.2" - } - }, - "node_modules/@shikijs/langs": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.2.1.tgz", - "integrity": "sha512-If0iDHYRSGbihiA8+7uRsgb1er1Yj11pwpX1c6HLYnizDsKAw5iaT3JXj5ZpaimXSWky/IhxTm7C6nkiYVym+A==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.2.1" - } - }, - "node_modules/@shikijs/themes": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.2.1.tgz", - "integrity": "sha512-k5DKJUT8IldBvAm8WcrDT5+7GA7se6lLksR+2E3SvyqGTyFMzU2F9Gb7rmD+t+Pga1MKrYFxDIeyWjMZWM6uBQ==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.2.1" - } - }, - "node_modules/@shikijs/types": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.2.1.tgz", - "integrity": "sha512-/NTWAk4KE2M8uac0RhOsIhYQf4pdU0OywQuYDGIGAJ6Mjunxl2cGiuLkvu4HLCMn+OTTLRWkjZITp+aYJv60yA==", - "license": "MIT", - "dependencies": { - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4" - } - }, - "node_modules/@shikijs/vscode-textmate": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", - "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", - "license": "MIT" - }, - "node_modules/@shuding/opentype.js": { - "version": "1.4.0-beta.0", - "resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz", - "integrity": "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==", - "license": "MIT", - "dependencies": { - "fflate": "^0.7.3", - "string.prototype.codepointat": "^0.2.1" - }, - "bin": { - "ot": "bin/ot" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/@stylistic/eslint-plugin": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-4.2.0.tgz", - "integrity": "sha512-8hXezgz7jexGHdo5WN6JBEIPHCSFyyU4vgbxevu4YLVS5vl+sxqAAGyXSzfNDyR6xMNSH5H1x67nsXcYMOHtZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/utils": "^8.23.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", - "estraverse": "^5.3.0", - "picomatch": "^4.0.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "peerDependencies": { - "eslint": ">=9.0.0" - } - }, - "node_modules/@swc/helpers": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", - "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" - } - }, - "node_modules/@tailwindcss/forms": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz", - "integrity": "sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==", - "dev": true, - "license": "MIT", - "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" - } - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.7.tgz", - "integrity": "sha512-9rsOpdY9idRI2NH6CL4wORFY0+Q6fnx9XP9Ju+iq/0wJwGD5IByIgFmwVbyy4ymuyprj8Qh4ErxMKTUL4uNh3g==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "enhanced-resolve": "^5.18.1", - "jiti": "^2.4.2", - "lightningcss": "1.30.1", - "magic-string": "^0.30.17", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.7" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.7.tgz", - "integrity": "sha512-5SF95Ctm9DFiUyjUPnDGkoKItPX/k+xifcQhcqX5RA85m50jw1pT/KzjdvlqxRja45Y52nR4MR9fD1JYd7f8NQ==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.4", - "tar": "^7.4.3" - }, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.7", - "@tailwindcss/oxide-darwin-arm64": "4.1.7", - "@tailwindcss/oxide-darwin-x64": "4.1.7", - "@tailwindcss/oxide-freebsd-x64": "4.1.7", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.7", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.7", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.7", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.7", - "@tailwindcss/oxide-linux-x64-musl": "4.1.7", - "@tailwindcss/oxide-wasm32-wasi": "4.1.7", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.7", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.7" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.7.tgz", - "integrity": "sha512-IWA410JZ8fF7kACus6BrUwY2Z1t1hm0+ZWNEzykKmMNM09wQooOcN/VXr0p/WJdtHZ90PvJf2AIBS/Ceqx1emg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.7.tgz", - "integrity": "sha512-81jUw9To7fimGGkuJ2W5h3/oGonTOZKZ8C2ghm/TTxbwvfSiFSDPd6/A/KE2N7Jp4mv3Ps9OFqg2fEKgZFfsvg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.7.tgz", - "integrity": "sha512-q77rWjEyGHV4PdDBtrzO0tgBBPlQWKY7wZK0cUok/HaGgbNKecegNxCGikuPJn5wFAlIywC3v+WMBt0PEBtwGw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.7.tgz", - "integrity": "sha512-RfmdbbK6G6ptgF4qqbzoxmH+PKfP4KSVs7SRlTwcbRgBwezJkAO3Qta/7gDy10Q2DcUVkKxFLXUQO6J3CRvBGw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.7.tgz", - "integrity": "sha512-OZqsGvpwOa13lVd1z6JVwQXadEobmesxQ4AxhrwRiPuE04quvZHWn/LnihMg7/XkN+dTioXp/VMu/p6A5eZP3g==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.7.tgz", - "integrity": "sha512-voMvBTnJSfKecJxGkoeAyW/2XRToLZ227LxswLAwKY7YslG/Xkw9/tJNH+3IVh5bdYzYE7DfiaPbRkSHFxY1xA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.7.tgz", - "integrity": "sha512-PjGuNNmJeKHnP58M7XyjJyla8LPo+RmwHQpBI+W/OxqrwojyuCQ+GUtygu7jUqTEexejZHr/z3nBc/gTiXBj4A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.7.tgz", - "integrity": "sha512-HMs+Va+ZR3gC3mLZE00gXxtBo3JoSQxtu9lobbZd+DmfkIxR54NO7Z+UQNPsa0P/ITn1TevtFxXTpsRU7qEvWg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.7.tgz", - "integrity": "sha512-MHZ6jyNlutdHH8rd+YTdr3QbXrHXqwIhHw9e7yXEBcQdluGwhpQY2Eku8UZK6ReLaWtQ4gijIv5QoM5eE+qlsA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.7.tgz", - "integrity": "sha512-ANaSKt74ZRzE2TvJmUcbFQ8zS201cIPxUDm5qez5rLEwWkie2SkGtA4P+GPTj+u8N6JbPrC8MtY8RmJA35Oo+A==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.9", - "@tybys/wasm-util": "^0.9.0", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.7.tgz", - "integrity": "sha512-HUiSiXQ9gLJBAPCMVRk2RT1ZrBjto7WvqsPBwUrNK2BcdSxMnk19h4pjZjI7zgPhDxlAbJSumTC4ljeA9y0tEw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.7.tgz", - "integrity": "sha512-rYHGmvoHiLJ8hWucSfSOEmdCBIGZIq7SpkPRSqLsH2Ab2YUNgKeAPT1Fi2cx3+hnYOrAb0jp9cRyode3bBW4mQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide/node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/@tailwindcss/oxide/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/@tailwindcss/oxide/node_modules/minizlib": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", - "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@tailwindcss/oxide/node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@tailwindcss/oxide/node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@tailwindcss/oxide/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/@tailwindcss/typography": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz", - "integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash.castarray": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.merge": "^4.6.2", - "postcss-selector-parser": "6.0.10" - }, - "peerDependencies": { - "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.7.tgz", - "integrity": "sha512-tYa2fO3zDe41I7WqijyVbRd8oWT0aEID1Eokz5hMT6wShLIHj3yvwj9XbfuloHP9glZ6H+aG2AN/+ZrxJ1Y5RQ==", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.7", - "@tailwindcss/oxide": "4.1.7", - "tailwindcss": "4.1.7" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6" - } - }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/@tybys/wasm-util": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", - "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/eslint__js": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/@types/eslint__js/-/eslint__js-9.14.0.tgz", - "integrity": "sha512-s0jepCjOJWB/GKcuba4jISaVpBudw3ClXJ3fUK4tugChUMQsp6kSwuA8Dcx6wFd/JsJqcY8n4rEpa5RTHs5ypA==", - "deprecated": "This is a stub types definition. @eslint/js provides its own type definitions, so you do not need this installed.", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint/js": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", - "license": "MIT" - }, - "node_modules/@types/estree-jsx": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", - "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/@types/fontkit": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@types/fontkit/-/fontkit-2.0.8.tgz", - "integrity": "sha512-wN+8bYxIpJf+5oZdrdtaX04qUuWHcKxcDEgRS9Qm9ZClSHjzEn13SxUC+5eRM+4yXIeTYk8mTzLAWGF64847ew==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/lodash-es": { - "version": "4.17.12", - "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", - "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/lodash": "*" - } - }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/mdx": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", - "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", - "license": "MIT" - }, - "node_modules/@types/mime-types": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", - "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", - "license": "MIT" - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT" - }, - "node_modules/@types/nlcst": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", - "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/node": { - "version": "22.10.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.9.tgz", - "integrity": "sha512-Ir6hwgsKyNESl/gLOcEz3krR4CBGgliDqBQ2ma4wIhEx0w+xnoeTq3tdrNw15kU3SxogDjOgv9sqdtLW8mIHaw==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "node_modules/@types/pg": { - "version": "8.6.1", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz", - "integrity": "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@types/node": "*", - "pg-protocol": "*", - "pg-types": "^2.2.0" - } - }, - "node_modules/@types/react": { - "version": "19.1.4", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.4.tgz", - "integrity": "sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "csstype": "^3.0.2" - } - }, - "node_modules/@types/sax": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", - "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/seedrandom": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-3.0.8.tgz", - "integrity": "sha512-TY1eezMU2zH2ozQoAFAQFOPpvP15g+ZgSfTZt31AUUH/Rxtnz3H+A/Sv1Snw2/amp//omibc+AEkTaA8KUeOLQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/tar": { - "version": "6.1.13", - "resolved": "https://registry.npmjs.org/@types/tar/-/tar-6.1.13.tgz", - "integrity": "sha512-IznnlmU5f4WcGTh2ltRu/Ijpmk8wiWXfF0VA4s+HPjHZgvFggk1YaIkbo5krX/zUCzWF8N/l4+W/LNxnvAJ8nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "minipass": "^4.0.0" - } - }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "license": "MIT" - }, - "node_modules/@types/ws": { - "version": "8.5.13", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", - "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.1.tgz", - "integrity": "sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.32.1", - "@typescript-eslint/type-utils": "8.32.1", - "@typescript-eslint/utils": "8.32.1", - "@typescript-eslint/visitor-keys": "8.32.1", - "graphemer": "^1.4.0", - "ignore": "^7.0.0", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz", - "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.32.1.tgz", - "integrity": "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.32.1", - "@typescript-eslint/types": "8.32.1", - "@typescript-eslint/typescript-estree": "8.32.1", - "@typescript-eslint/visitor-keys": "8.32.1", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.32.1.tgz", - "integrity": "sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.32.1", - "@typescript-eslint/visitor-keys": "8.32.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.32.1.tgz", - "integrity": "sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "8.32.1", - "@typescript-eslint/utils": "8.32.1", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.1.tgz", - "integrity": "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.1.tgz", - "integrity": "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.32.1", - "@typescript-eslint/visitor-keys": "8.32.1", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.32.1.tgz", - "integrity": "sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.32.1", - "@typescript-eslint/types": "8.32.1", - "@typescript-eslint/typescript-estree": "8.32.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.1.tgz", - "integrity": "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.32.1", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.1.tgz", - "integrity": "sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==", - "license": "ISC" - }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.7.2.tgz", - "integrity": "sha512-vxtBno4xvowwNmO/ASL0Y45TpHqmNkAaDtz4Jqb+clmcVSSl8XCG/PNFFkGsXXXS6AMjP+ja/TtNCFFa1QwLRg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.7.2.tgz", - "integrity": "sha512-qhVa8ozu92C23Hsmv0BF4+5Dyyd5STT1FolV4whNgbY6mj3kA0qsrGPe35zNR3wAN7eFict3s4Rc2dDTPBTuFQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.7.2.tgz", - "integrity": "sha512-zKKdm2uMXqLFX6Ac7K5ElnnG5VIXbDlFWzg4WJ8CGUedJryM5A3cTgHuGMw1+P5ziV8CRhnSEgOnurTI4vpHpg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.7.2.tgz", - "integrity": "sha512-8N1z1TbPnHH+iDS/42GJ0bMPLiGK+cUqOhNbMKtWJ4oFGzqSJk/zoXFzcQkgtI63qMcUI7wW1tq2usZQSb2jxw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.7.2.tgz", - "integrity": "sha512-tjYzI9LcAXR9MYd9rO45m1s0B/6bJNuZ6jeOxo1pq1K6OBuRMMmfyvJYval3s9FPPGmrldYA3mi4gWDlWuTFGA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.7.2.tgz", - "integrity": "sha512-jon9M7DKRLGZ9VYSkFMflvNqu9hDtOCEnO2QAryFWgT6o6AXU8du56V7YqnaLKr6rAbZBWYsYpikF226v423QA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.7.2.tgz", - "integrity": "sha512-c8Cg4/h+kQ63pL43wBNaVMmOjXI/X62wQmru51qjfTvI7kmCy5uHTJvK/9LrF0G8Jdx8r34d019P1DVJmhXQpA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.7.2.tgz", - "integrity": "sha512-A+lcwRFyrjeJmv3JJvhz5NbcCkLQL6Mk16kHTNm6/aGNc4FwPHPE4DR9DwuCvCnVHvF5IAd9U4VIs/VvVir5lg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.7.2.tgz", - "integrity": "sha512-hQQ4TJQrSQW8JlPm7tRpXN8OCNP9ez7PajJNjRD1ZTHQAy685OYqPrKjfaMw/8LiHCt8AZ74rfUVHP9vn0N69Q==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.7.2.tgz", - "integrity": "sha512-NoAGbiqrxtY8kVooZ24i70CjLDlUFI7nDj3I9y54U94p+3kPxwd2L692YsdLa+cqQ0VoqMWoehDFp21PKRUoIQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.7.2.tgz", - "integrity": "sha512-KaZByo8xuQZbUhhreBTW+yUnOIHUsv04P8lKjQ5otiGoSJ17ISGYArc+4vKdLEpGaLbemGzr4ZeUbYQQsLWFjA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.7.2.tgz", - "integrity": "sha512-dEidzJDubxxhUCBJ/SHSMJD/9q7JkyfBMT77Px1npl4xpg9t0POLvnWywSk66BgZS/b2Hy9Y1yFaoMTFJUe9yg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.7.2.tgz", - "integrity": "sha512-RvP+Ux3wDjmnZDT4XWFfNBRVG0fMsc+yVzNFUqOflnDfZ9OYujv6nkh+GOr+watwrW4wdp6ASfG/e7bkDradsw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.7.2.tgz", - "integrity": "sha512-y797JBmO9IsvXVRCKDXOxjyAE4+CcZpla2GSoBQ33TVb3ILXuFnMrbR/QQZoauBYeOFuu4w3ifWLw52sdHGz6g==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.9" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.7.2.tgz", - "integrity": "sha512-gtYTh4/VREVSLA+gHrfbWxaMO/00y+34htY7XpioBTy56YN2eBjkPrY1ML1Zys89X3RJDKVaogzwxlM1qU7egg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.7.2.tgz", - "integrity": "sha512-Ywv20XHvHTDRQs12jd3MY8X5C8KLjDbg/jyaal/QLKx3fAShhJyD4blEANInsjxW3P7isHx1Blt56iUDDJO3jg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.7.2.tgz", - "integrity": "sha512-friS8NEQfHaDbkThxopGk+LuE5v3iY0StruifjQEt7SLbA46OnfgMO15sOTkbpJkol6RB+1l1TYPXh0sCddpvA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@vercel/og": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/@vercel/og/-/og-0.6.8.tgz", - "integrity": "sha512-e4kQK9mP8ntpo3dACWirGod/hHv4qO5JMj9a/0a2AZto7b4persj5YP7t1Er372gTtYFTYxNhMx34jRvHooglw==", - "license": "MPL-2.0", - "dependencies": { - "@resvg/resvg-wasm": "2.4.0", - "satori": "0.12.2", - "yoga-wasm-web": "0.3.3" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@volar/kit": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/@volar/kit/-/kit-2.4.12.tgz", - "integrity": "sha512-f9JE8oy9C2rBcCWxUYKUF23hOXz4mwgVXFjk7nHhxzplaoVjEOsKpBm8NI2nBH7Cwu8DRxDwBsbIxMl/8wlLxw==", - "license": "MIT", - "dependencies": { - "@volar/language-service": "2.4.12", - "@volar/typescript": "2.4.12", - "typesafe-path": "^0.2.2", - "vscode-languageserver-textdocument": "^1.0.11", - "vscode-uri": "^3.0.8" - }, - "peerDependencies": { - "typescript": "*" - } - }, - "node_modules/@volar/language-core": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.12.tgz", - "integrity": "sha512-RLrFdXEaQBWfSnYGVxvR2WrO6Bub0unkdHYIdC31HzIEqATIuuhRRzYu76iGPZ6OtA4Au1SnW0ZwIqPP217YhA==", - "license": "MIT", - "dependencies": { - "@volar/source-map": "2.4.12" - } - }, - "node_modules/@volar/language-server": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/@volar/language-server/-/language-server-2.4.12.tgz", - "integrity": "sha512-KC0YqTXCZMaImMWyAKC+dLB2BXjfz80kqesJkV6oXxJsGEQPfmdqug299idwtrT6FVSmZ7q5UrPfvgKwA0S3JA==", - "license": "MIT", - "dependencies": { - "@volar/language-core": "2.4.12", - "@volar/language-service": "2.4.12", - "@volar/typescript": "2.4.12", - "path-browserify": "^1.0.1", - "request-light": "^0.7.0", - "vscode-languageserver": "^9.0.1", - "vscode-languageserver-protocol": "^3.17.5", - "vscode-languageserver-textdocument": "^1.0.11", - "vscode-uri": "^3.0.8" - } - }, - "node_modules/@volar/language-service": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/@volar/language-service/-/language-service-2.4.12.tgz", - "integrity": "sha512-nifOPGYYPnCmxja6/ML/Gl2EgFkUdw4gLbYqbh8FjqX3gSpXSZl/0ebqORjKo1KW56YWHWRZd1jFutEtCiRYhA==", - "license": "MIT", - "dependencies": { - "@volar/language-core": "2.4.12", - "vscode-languageserver-protocol": "^3.17.5", - "vscode-languageserver-textdocument": "^1.0.11", - "vscode-uri": "^3.0.8" - } - }, - "node_modules/@volar/source-map": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.12.tgz", - "integrity": "sha512-bUFIKvn2U0AWojOaqf63ER0N/iHIBYZPpNGogfLPQ68F5Eet6FnLlyho7BS0y2HJ1jFhSif7AcuTx1TqsCzRzw==", - "license": "MIT" - }, - "node_modules/@volar/typescript": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.12.tgz", - "integrity": "sha512-HJB73OTJDgPc80K30wxi3if4fSsZZAOScbj2fcicMuOPoOkcf9NNAINb33o+DzhBdF9xTKC1gnPmIRDous5S0g==", - "license": "MIT", - "dependencies": { - "@volar/language-core": "2.4.12", - "path-browserify": "^1.0.1", - "vscode-uri": "^3.0.8" - } - }, - "node_modules/@vscode/emmet-helper": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@vscode/emmet-helper/-/emmet-helper-2.11.0.tgz", - "integrity": "sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==", - "license": "MIT", - "dependencies": { - "emmet": "^2.4.3", - "jsonc-parser": "^2.3.0", - "vscode-languageserver-textdocument": "^1.0.1", - "vscode-languageserver-types": "^3.15.1", - "vscode-uri": "^3.0.8" - } - }, - "node_modules/@vscode/l10n": { - "version": "0.0.18", - "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.18.tgz", - "integrity": "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==", - "license": "MIT" - }, - "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "license": "ISC", - "dependencies": { - "string-width": "^4.1.0" - } - }, - "node_modules/ansi-align/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-align/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-align/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-includes": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", - "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-iterate": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", - "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", - "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", - "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", - "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/ast-types-flow": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", - "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/astring": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", - "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", - "license": "MIT", - "bin": { - "astring": "bin/astring" - } - }, - "node_modules/astro": { - "version": "5.7.13", - "resolved": "https://registry.npmjs.org/astro/-/astro-5.7.13.tgz", - "integrity": "sha512-cRGq2llKOhV3XMcYwQpfBIUcssN6HEK5CRbcMxAfd9OcFhvWE7KUy50zLioAZVVl3AqgUTJoNTlmZfD2eG0G1w==", - "license": "MIT", - "dependencies": { - "@astrojs/compiler": "^2.11.0", - "@astrojs/internal-helpers": "0.6.1", - "@astrojs/markdown-remark": "6.3.1", - "@astrojs/telemetry": "3.2.1", - "@capsizecss/unpack": "^2.4.0", - "@oslojs/encoding": "^1.1.0", - "@rollup/pluginutils": "^5.1.4", - "acorn": "^8.14.1", - "aria-query": "^5.3.2", - "axobject-query": "^4.1.0", - "boxen": "8.0.1", - "ci-info": "^4.2.0", - "clsx": "^2.1.1", - "common-ancestor-path": "^1.0.1", - "cookie": "^1.0.2", - "cssesc": "^3.0.0", - "debug": "^4.4.0", - "deterministic-object-hash": "^2.0.2", - "devalue": "^5.1.1", - "diff": "^5.2.0", - "dlv": "^1.1.3", - "dset": "^3.1.4", - "es-module-lexer": "^1.6.0", - "esbuild": "^0.25.0", - "estree-walker": "^3.0.3", - "flattie": "^1.1.1", - "fontace": "~0.3.0", - "github-slugger": "^2.0.0", - "html-escaper": "3.0.3", - "http-cache-semantics": "^4.1.1", - "js-yaml": "^4.1.0", - "kleur": "^4.1.5", - "magic-string": "^0.30.17", - "magicast": "^0.3.5", - "mrmime": "^2.0.1", - "neotraverse": "^0.6.18", - "p-limit": "^6.2.0", - "p-queue": "^8.1.0", - "package-manager-detector": "^1.1.0", - "picomatch": "^4.0.2", - "prompts": "^2.4.2", - "rehype": "^13.0.2", - "semver": "^7.7.1", - "shiki": "^3.2.1", - "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.12", - "tsconfck": "^3.1.5", - "ultrahtml": "^1.6.0", - "unifont": "~0.5.0", - "unist-util-visit": "^5.0.0", - "unstorage": "^1.15.0", - "vfile": "^6.0.3", - "vite": "^6.3.4", - "vitefu": "^1.0.6", - "xxhash-wasm": "^1.1.0", - "yargs-parser": "^21.1.1", - "yocto-spinner": "^0.2.1", - "zod": "^3.24.2", - "zod-to-json-schema": "^3.24.5", - "zod-to-ts": "^1.2.0" - }, - "bin": { - "astro": "astro.js" - }, - "engines": { - "node": "^18.17.1 || ^20.3.0 || >=22.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/astrodotbuild" - }, - "optionalDependencies": { - "sharp": "^0.33.3" - } - }, - "node_modules/astro-eslint-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/astro-eslint-parser/-/astro-eslint-parser-1.2.1.tgz", - "integrity": "sha512-3oqANMjrvJ+IE5pwlUWsH/4UztmYf/GTL0HPUkWnYBNAHiGVGrOh2EbegxS5niAwlO0w9dRYk0CkCPlJcu8c3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@astrojs/compiler": "^2.0.0", - "@typescript-eslint/scope-manager": "^7.0.0 || ^8.0.0", - "@typescript-eslint/types": "^7.0.0 || ^8.0.0", - "astrojs-compiler-sync": "^1.0.0", - "debug": "^4.3.4", - "entities": "^6.0.0", - "eslint-scope": "^8.0.1", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.0.0", - "fast-glob": "^3.3.3", - "is-glob": "^4.0.3", - "semver": "^7.3.8" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://github.com/sponsors/ota-meshi" - } - }, - "node_modules/astro-eslint-parser/node_modules/entities": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", - "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/astro-icon": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/astro-icon/-/astro-icon-1.1.5.tgz", - "integrity": "sha512-CJYS5nWOw9jz4RpGWmzNQY7D0y2ZZacH7atL2K9DeJXJVaz7/5WrxeyIxO8KASk1jCM96Q4LjRx/F3R+InjJrw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@iconify/tools": "^4.0.5", - "@iconify/types": "^2.0.0", - "@iconify/utils": "^2.1.30" - } - }, - "node_modules/astro-loading-indicator": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/astro-loading-indicator/-/astro-loading-indicator-0.7.0.tgz", - "integrity": "sha512-oNSUYbHq8Aaol0zcVuBzGz+qrqKsviOvKl1xaaKo3DLkEef7HCzHQAgTgWv4lB1iYFWa5GaieQvlxBO4KjzKZg==", - "license": "MIT", - "peerDependencies": { - "astro": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/astro-remote": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/astro-remote/-/astro-remote-0.3.4.tgz", - "integrity": "sha512-jL5skNQLA0YBc1R3bVGXyHew3FqGqsT7AgLzWAVeTLzFkwVMUYvs4/lKJSmS7ygcF1GnHnoKG6++8GL9VtWwGQ==", - "license": "MIT", - "dependencies": { - "entities": "^4.5.0", - "marked": "^12.0.0", - "marked-footnote": "^1.2.2", - "marked-smartypants": "^1.1.6", - "ultrahtml": "^1.5.3" - }, - "engines": { - "node": ">=18.14.1" - } - }, - "node_modules/astro-seo-schema": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/astro-seo-schema/-/astro-seo-schema-5.0.0.tgz", - "integrity": "sha512-rbP9BUxvqH4tdgG5VEYDcoAxUhVKv93TdHRQ5zbYUxee1TKAWsNcAG3nqavX83QTTGqU5anvikMcJZXWhGN9wg==", - "license": "MIT", - "peerDependencies": { - "astro": "^5.0.0", - "schema-dts": "^1.1.0" - } - }, - "node_modules/astro/node_modules/package-manager-detector": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.1.0.tgz", - "integrity": "sha512-Y8f9qUlBzW8qauJjd/eu6jlpJZsuPJm2ZAV0cDVd420o4EdpH5RPdoCv+60/TdJflGatr4sDfpAL6ArWZbM5tA==", - "license": "MIT" - }, - "node_modules/astrojs-compiler-sync": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/astrojs-compiler-sync/-/astrojs-compiler-sync-1.0.1.tgz", - "integrity": "sha512-EdJILVkc/Iiw9sLMyb2uppp/vG7YL9TgkwaEumNDflI8s0AhR5XuCFkdbA/AcCGvcBfsRH9ngy/iIP8Uybl82g==", - "dev": true, - "license": "MIT", - "dependencies": { - "synckit": "^0.9.0" - }, - "engines": { - "node": "^18.18.0 || >=20.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ota-meshi" - }, - "peerDependencies": { - "@astrojs/compiler": ">=0.27.0" - } - }, - "node_modules/async-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/async-listen": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/async-listen/-/async-listen-3.1.0.tgz", - "integrity": "sha512-TkOhqze98lP+6e7SPbrBpyhTpfvqqX8VYKGn4uckrgPan4WQIHnTaUD2zZzZS18eVVDj4rHPcIZa1PGgvo1DfA==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/axe-core": { - "version": "4.10.2", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.2.tgz", - "integrity": "sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==", - "dev": true, - "license": "MPL-2.0", - "engines": { - "node": ">=4" - } - }, - "node_modules/axios": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", - "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", - "dev": true, - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/bail": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/base-64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", - "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/blob-to-buffer": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/blob-to-buffer/-/blob-to-buffer-1.2.9.tgz", - "integrity": "sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true, - "license": "ISC" - }, - "node_modules/boxen": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", - "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", - "license": "MIT", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^8.0.0", - "chalk": "^5.3.0", - "cli-boxes": "^3.0.0", - "string-width": "^7.2.0", - "type-fest": "^4.21.0", - "widest-line": "^5.0.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/brotli": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", - "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", - "license": "MIT", - "dependencies": { - "base64-js": "^1.1.2" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/bundle-name": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", - "license": "MIT", - "dependencies": { - "run-applescript": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", - "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", - "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", - "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelize": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", - "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/canvas": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/canvas/-/canvas-3.1.0.tgz", - "integrity": "sha512-tTj3CqqukVJ9NgSahykNwtGda7V33VLObwrHfzT0vqJXu7J4d4C/7kQQW3fOEGDfZZoILPut5H00gOjyttPGyg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "node-addon-api": "^7.0.0", - "prebuild-install": "^7.1.1" - }, - "engines": { - "node": "^18.12.0 || >= 20.9.0" - } - }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-reference-invalid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", - "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/cheerio": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", - "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.1.0", - "encoding-sniffer": "^0.2.0", - "htmlparser2": "^9.1.0", - "parse5": "^7.1.2", - "parse5-htmlparser2-tree-adapter": "^7.0.0", - "parse5-parser-stream": "^7.1.2", - "undici": "^6.19.5", - "whatwg-mimetype": "^4.0.0" - }, - "engines": { - "node": ">=18.17" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, - "node_modules/cheerio-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", - "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-select": "^5.1.0", - "css-what": "^6.1.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/ci-info": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.2.0.tgz", - "integrity": "sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-boxes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/cluster-key-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", - "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/collapse-white-space": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", - "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "license": "MIT", - "optional": true, - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "license": "MIT", - "optional": true, - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/common-ancestor-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", - "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", - "license": "ISC" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/cookie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", - "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/cookie-es": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", - "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", - "license": "MIT" - }, - "node_modules/cross-fetch": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", - "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", - "license": "MIT", - "dependencies": { - "node-fetch": "^2.7.0" - } - }, - "node_modules/cross-fetch/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crossws": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.4.tgz", - "integrity": "sha512-uj0O1ETYX1Bh6uSgktfPvwDiPYGQ3aI4qVsaC/LWpkIzGj1nUYm5FK3K+t11oOlpN01lGbprFCH4wBlKdJjVgw==", - "license": "MIT", - "dependencies": { - "uncrypto": "^0.1.3" - } - }, - "node_modules/css-background-parser": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/css-background-parser/-/css-background-parser-0.1.0.tgz", - "integrity": "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==", - "license": "MIT" - }, - "node_modules/css-box-shadow": { - "version": "1.0.0-3", - "resolved": "https://registry.npmjs.org/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz", - "integrity": "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==", - "license": "MIT" - }, - "node_modules/css-color-keywords": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", - "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", - "license": "ISC", - "engines": { - "node": ">=4" - } - }, - "node_modules/css-gradient-parser": { - "version": "0.0.16", - "resolved": "https://registry.npmjs.org/css-gradient-parser/-/css-gradient-parser-0.0.16.tgz", - "integrity": "sha512-3O5QdqgFRUbXvK1x5INf1YkBz1UKSWqrd63vWsum8MNHDBYD5urm3QtxZbKU259OrEXNM26lP/MPY3d1IGkBgA==", - "license": "MIT", - "engines": { - "node": ">=16" - } - }, - "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-to-react-native": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", - "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", - "license": "MIT", - "dependencies": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^4.0.2" - } - }, - "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/csso": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", - "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "css-tree": "~2.2.0" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/css-tree": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", - "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", - "dev": true, - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.28", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/mdn-data": { - "version": "2.0.28", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", - "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/damerau-levenshtein": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/date-fns": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", - "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/kossnocorp" - } - }, - "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decode-named-character-reference": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz", - "integrity": "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==", - "license": "MIT", - "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-diff": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-1.0.2.tgz", - "integrity": "sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==", - "license": "MIT" - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/default-browser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", - "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", - "license": "MIT", - "dependencies": { - "bundle-name": "^4.1.0", - "default-browser-id": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", - "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/defu": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", - "license": "MIT" - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/destr": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz", - "integrity": "sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==", - "license": "MIT" - }, - "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/deterministic-object-hash": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/deterministic-object-hash/-/deterministic-object-hash-2.0.2.tgz", - "integrity": "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==", - "license": "MIT", - "dependencies": { - "base-64": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/devalue": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", - "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", - "license": "MIT" - }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "license": "MIT", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/dfa": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", - "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", - "license": "MIT" - }, - "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "license": "MIT" - }, - "node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/drizzle-orm": { - "version": "0.31.4", - "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.31.4.tgz", - "integrity": "sha512-VGD9SH9aStF2z4QOTnVlVX/WghV/EnuEzTmsH3fSVp2E4fFgc8jl3viQrS/XUJx1ekW4rVVLJMH42SfGQdjX3Q==", - "license": "Apache-2.0", - "peerDependencies": { - "@aws-sdk/client-rds-data": ">=3", - "@cloudflare/workers-types": ">=3", - "@electric-sql/pglite": ">=0.1.1", - "@libsql/client": "*", - "@neondatabase/serverless": ">=0.1", - "@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": ">=13.2.0", - "knex": "*", - "kysely": "*", - "mysql2": ">=2", - "pg": ">=8", - "postgres": ">=3", - "react": ">=18", - "sql.js": ">=1", - "sqlite3": ">=5" - }, - "peerDependenciesMeta": { - "@aws-sdk/client-rds-data": { - "optional": true - }, - "@cloudflare/workers-types": { - "optional": true - }, - "@electric-sql/pglite": { - "optional": true - }, - "@libsql/client": { - "optional": true - }, - "@neondatabase/serverless": { - "optional": true - }, - "@op-engineering/op-sqlite": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@planetscale/database": { - "optional": true - }, - "@prisma/client": { - "optional": true - }, - "@tidbcloud/serverless": { - "optional": true - }, - "@types/better-sqlite3": { - "optional": true - }, - "@types/pg": { - "optional": true - }, - "@types/react": { - "optional": true - }, - "@types/sql.js": { - "optional": true - }, - "@vercel/postgres": { - "optional": true - }, - "@xata.io/client": { - "optional": true - }, - "better-sqlite3": { - "optional": true - }, - "bun-types": { - "optional": true - }, - "expo-sqlite": { - "optional": true - }, - "knex": { - "optional": true - }, - "kysely": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "pg": { - "optional": true - }, - "postgres": { - "optional": true - }, - "prisma": { - "optional": true - }, - "react": { - "optional": true - }, - "sql.js": { - "optional": true - }, - "sqlite3": { - "optional": true - } - } - }, - "node_modules/dset": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", - "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/emmet": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/emmet/-/emmet-2.4.11.tgz", - "integrity": "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==", - "license": "MIT", - "workspaces": [ - "./packages/scanner", - "./packages/abbreviation", - "./packages/css-abbreviation", - "./" - ], - "dependencies": { - "@emmetio/abbreviation": "^2.3.3", - "@emmetio/css-abbreviation": "^2.1.8" - } - }, - "node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "license": "MIT" - }, - "node_modules/emoji-regex-xs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", - "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/encoding-sniffer": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", - "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "iconv-lite": "^0.6.3", - "whatwg-encoding": "^3.1.1" - }, - "funding": { - "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.18.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", - "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/es-abstract": { - "version": "1.23.9", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", - "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.0", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-regex": "^1.2.1", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.0", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.3", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.18" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", - "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", - "license": "MIT" - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-shim-unscopables": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", - "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/esast-util-from-estree": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", - "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-visit": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/esast-util-from-js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", - "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "acorn": "^8.0.0", - "esast-util-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/esbuild": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", - "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.0", - "@esbuild/android-arm": "0.25.0", - "@esbuild/android-arm64": "0.25.0", - "@esbuild/android-x64": "0.25.0", - "@esbuild/darwin-arm64": "0.25.0", - "@esbuild/darwin-x64": "0.25.0", - "@esbuild/freebsd-arm64": "0.25.0", - "@esbuild/freebsd-x64": "0.25.0", - "@esbuild/linux-arm": "0.25.0", - "@esbuild/linux-arm64": "0.25.0", - "@esbuild/linux-ia32": "0.25.0", - "@esbuild/linux-loong64": "0.25.0", - "@esbuild/linux-mips64el": "0.25.0", - "@esbuild/linux-ppc64": "0.25.0", - "@esbuild/linux-riscv64": "0.25.0", - "@esbuild/linux-s390x": "0.25.0", - "@esbuild/linux-x64": "0.25.0", - "@esbuild/netbsd-arm64": "0.25.0", - "@esbuild/netbsd-x64": "0.25.0", - "@esbuild/openbsd-arm64": "0.25.0", - "@esbuild/openbsd-x64": "0.25.0", - "@esbuild/sunos-x64": "0.25.0", - "@esbuild/win32-arm64": "0.25.0", - "@esbuild/win32-ia32": "0.25.0", - "@esbuild/win32-x64": "0.25.0" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.27.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz", - "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", - "@eslint/config-helpers": "^0.2.1", - "@eslint/core": "^0.14.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.27.0", - "@eslint/plugin-kit": "^0.3.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-compat-utils": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.6.4.tgz", - "integrity": "sha512-/u+GQt8NMfXO8w17QendT4gvO5acfxQsAKirAt0LVxDnr2N8YLCVbregaNc/Yhp7NM128DwCaRvr8PLDfeNkQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "eslint": ">=6.0.0" - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-import-resolver-typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.3.5.tgz", - "integrity": "sha512-QGwhLrwn/WGOsdrWvjhm9n8BvKN/Wr41SQERMV7DQ2hm9+Ozas39CyQUxum///l2G2vefQVr7VbIaCFS5h9g5g==", - "dev": true, - "license": "ISC", - "dependencies": { - "debug": "^4.4.0", - "get-tsconfig": "^4.10.0", - "is-bun-module": "^2.0.0", - "stable-hash": "^0.0.5", - "tinyglobby": "^0.2.13", - "unrs-resolver": "^1.6.3" - }, - "engines": { - "node": "^16.17.0 || >=18.6.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-import-resolver-typescript" - }, - "peerDependencies": { - "eslint": "*", - "eslint-plugin-import": "*", - "eslint-plugin-import-x": "*" - }, - "peerDependenciesMeta": { - "eslint-plugin-import": { - "optional": true - }, - "eslint-plugin-import-x": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", - "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-astro": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-astro/-/eslint-plugin-astro-1.3.1.tgz", - "integrity": "sha512-2XaLCMQm8htW1UvJvy1Zcmg8l0ziskitiUfJTn/w1Mk7r4Mxj0fZeNpN6UTNrm64XBIXSa5h8UCGrg8mdu47+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@jridgewell/sourcemap-codec": "^1.4.14", - "@typescript-eslint/types": "^7.7.1 || ^8", - "astro-eslint-parser": "^1.0.2", - "eslint-compat-utils": "^0.6.0", - "globals": "^15.0.0", - "postcss": "^8.4.14", - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://github.com/sponsors/ota-meshi" - }, - "peerDependencies": { - "eslint": ">=8.57.0" - } - }, - "node_modules/eslint-plugin-astro/node_modules/globals": { - "version": "15.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", - "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint-plugin-astro/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.31.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", - "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.8", - "array.prototype.findlastindex": "^1.2.5", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.12.0", - "hasown": "^2.0.2", - "is-core-module": "^2.15.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "object.groupby": "^1.0.3", - "object.values": "^1.2.0", - "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.8", - "tsconfig-paths": "^3.15.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" - } - }, - "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-jsx-a11y": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", - "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "aria-query": "^5.3.2", - "array-includes": "^3.1.8", - "array.prototype.flatmap": "^1.3.2", - "ast-types-flow": "^0.0.8", - "axe-core": "^4.10.0", - "axobject-query": "^4.1.0", - "damerau-levenshtein": "^1.0.8", - "emoji-regex": "^9.2.2", - "hasown": "^2.0.2", - "jsx-ast-utils": "^3.3.5", - "language-tags": "^1.0.9", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "safe-regex-test": "^1.0.3", - "string.prototype.includes": "^2.0.1" - }, - "engines": { - "node": ">=4.0" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" - } - }, - "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint-plugin-jsx-a11y/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.14.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-util-attach-comments": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", - "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-build-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", - "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "estree-walker": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-is-identifier-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", - "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-scope": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", - "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-to-js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", - "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "astring": "^1.8.0", - "source-map": "^0.7.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-visit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", - "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "license": "MIT" - }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "license": "(MIT OR WTFPL)", - "engines": { - "node": ">=6" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastq": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", - "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/fflate": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", - "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==", - "license": "MIT" - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", - "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", - "dev": true, - "license": "ISC" - }, - "node_modules/flattie": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", - "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/fontace": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.3.0.tgz", - "integrity": "sha512-czoqATrcnxgWb/nAkfyIrRp6Q8biYj7nGnL6zfhTcX+JKKpWHFBnb8uNMw/kZr7u++3Y3wYSYoZgHkCcsuBpBg==", - "license": "MIT", - "dependencies": { - "@types/fontkit": "^2.0.8", - "fontkit": "^2.0.4" - } - }, - "node_modules/fontkit": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz", - "integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==", - "license": "MIT", - "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" - } - }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/form-data/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/form-data/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "license": "MIT", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" - }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", - "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", - "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "function-bind": "^1.1.2", - "get-proto": "^1.0.0", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-symbol-description": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-tsconfig": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", - "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" - }, - "node_modules/github-slugger": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", - "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", - "license": "ISC" - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globals": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.1.0.tgz", - "integrity": "sha512-aibexHNbb/jiUSObBgpHLj+sIuUmJnYcgXBlrfsiDZ9rt4aF2TFRbyLgZ2iFQuVZ1K5Mx3FVkbKRSgKrbK3K2g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/h3": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.1.tgz", - "integrity": "sha512-+ORaOBttdUm1E2Uu/obAyCguiI7MbBvsLTndc3gyK3zU+SYLoZXlyCP9Xgy0gikkGufFLTZXCXD6+4BsufnmHA==", - "license": "MIT", - "dependencies": { - "cookie-es": "^1.2.2", - "crossws": "^0.3.3", - "defu": "^6.1.4", - "destr": "^2.0.3", - "iron-webcrypto": "^1.2.1", - "node-mock-http": "^1.0.0", - "radix3": "^1.1.2", - "ufo": "^1.5.4", - "uncrypto": "^0.1.3" - } - }, - "node_modules/has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hast-util-from-html": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", - "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "devlop": "^1.1.0", - "hast-util-from-parse5": "^8.0.0", - "parse5": "^7.0.0", - "vfile": "^6.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-from-parse5": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.2.tgz", - "integrity": "sha512-SfMzfdAi/zAoZ1KkFEyyeXBn7u/ShQrfd675ZEE9M3qj+PMFX05xubzRyF76CCSJu8au9jgVxDV1+okFvgZU4A==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "hastscript": "^9.0.0", - "property-information": "^6.0.0", - "vfile": "^6.0.0", - "vfile-location": "^5.0.0", - "web-namespaces": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-is-element": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", - "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-parse-selector": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", - "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-raw": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", - "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "@ungap/structured-clone": "^1.0.0", - "hast-util-from-parse5": "^8.0.0", - "hast-util-to-parse5": "^8.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "parse5": "^7.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-estree": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", - "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-attach-comments": "^3.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-js": "^1.0.0", - "unist-util-position": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-estree/node_modules/property-information": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/hast-util-to-html": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", - "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-whitespace": "^3.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.0", - "zwitch": "^2.0.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-html/node_modules/property-information": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.0.0.tgz", - "integrity": "sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/hast-util-to-jsx-runtime": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", - "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-js": "^1.0.0", - "unist-util-position": "^5.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-jsx-runtime/node_modules/property-information": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/hast-util-to-parse5": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", - "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-text": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", - "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "hast-util-is-element": "^3.0.0", - "unist-util-find-after": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hastscript": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.0.tgz", - "integrity": "sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^4.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hex-rgb": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/hex-rgb/-/hex-rgb-4.3.0.tgz", - "integrity": "sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/html-escaper": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", - "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", - "license": "MIT" - }, - "node_modules/html-void-elements": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", - "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/htmlparser2": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", - "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.1.0", - "entities": "^4.5.0" - } - }, - "node_modules/htmx.org": { - "version": "1.9.12", - "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.12.tgz", - "integrity": "sha512-VZAohXyF7xPGS52IM8d1T1283y+X4D+Owf3qY1NZ9RuBypyu9l8cGsxUMAG5fEAb/DhT7rDoJ9Hpu5/HxFD3cw==", - "license": "0BSD" - }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "license": "BSD-2-Clause" - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-meta-resolve": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", - "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, - "node_modules/inline-style-parser": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", - "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", - "license": "MIT" - }, - "node_modules/internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/iron-webcrypto": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", - "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/brc-dd" - } - }, - "node_modules/is-alphabetical": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", - "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-alphanumerical": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", - "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", - "license": "MIT", - "dependencies": { - "is-alphabetical": "^2.0.0", - "is-decimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "license": "MIT", - "optional": true - }, - "node_modules/is-async-function": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bun-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", - "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.7.1" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-view": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-decimal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", - "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-function": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", - "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-proto": "^1.0.0", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-hexadecimal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", - "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", - "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/javascript-time-ago": { - "version": "2.5.11", - "resolved": "https://registry.npmjs.org/javascript-time-ago/-/javascript-time-ago-2.5.11.tgz", - "integrity": "sha512-Zeyf5R7oM1fSMW9zsU3YgAYwE0bimEeF54Udn2ixGd8PUwu+z1Yc5t4Y8YScJDMHD6uCx6giLt3VJR5K4CMwbg==", - "license": "MIT", - "dependencies": { - "relative-time-format": "^1.1.6" - } - }, - "node_modules/jiti": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", - "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/js-base64": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", - "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==", - "license": "BSD-3-Clause" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/jsonc-parser": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz", - "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==", - "license": "MIT" - }, - "node_modules/jsx-ast-utils": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", - "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "object.assign": "^4.1.4", - "object.values": "^1.1.6" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/kolorist": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", - "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/language-subtag-registry": { - "version": "0.3.23", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", - "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/language-tags": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", - "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", - "dev": true, - "license": "MIT", - "dependencies": { - "language-subtag-registry": "^0.3.20" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/libphonenumber-js": { - "version": "1.12.8", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.8.tgz", - "integrity": "sha512-f1KakiQJa9tdc7w1phC2ST+DyxWimy9c3g3yeF+84QtEanJr2K77wAmBPP22riU05xldniHsvXuflnLZ4oysqA==", - "license": "MIT" - }, - "node_modules/libsql": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/libsql/-/libsql-0.5.4.tgz", - "integrity": "sha512-GEFeWca4SDAQFxjHWJBE6GK52LEtSskiujbG3rqmmeTO9t4sfSBKIURNLLpKDDF7fb7jmTuuRkDAn9BZGITQNw==", - "cpu": [ - "x64", - "arm64", - "wasm32" - ], - "license": "MIT", - "os": [ - "darwin", - "linux", - "win32" - ], - "dependencies": { - "@neon-rs/load": "^0.0.4", - "detect-libc": "2.0.2" - }, - "optionalDependencies": { - "@libsql/darwin-arm64": "0.5.4", - "@libsql/darwin-x64": "0.5.4", - "@libsql/linux-arm64-gnu": "0.5.4", - "@libsql/linux-arm64-musl": "0.5.4", - "@libsql/linux-x64-gnu": "0.5.4", - "@libsql/linux-x64-musl": "0.5.4", - "@libsql/win32-x64-msvc": "0.5.4" - } - }, - "node_modules/libsql/node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/lightningcss": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", - "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.30.1", - "lightningcss-darwin-x64": "1.30.1", - "lightningcss-freebsd-x64": "1.30.1", - "lightningcss-linux-arm-gnueabihf": "1.30.1", - "lightningcss-linux-arm64-gnu": "1.30.1", - "lightningcss-linux-arm64-musl": "1.30.1", - "lightningcss-linux-x64-gnu": "1.30.1", - "lightningcss-linux-x64-musl": "1.30.1", - "lightningcss-win32-arm64-msvc": "1.30.1", - "lightningcss-win32-x64-msvc": "1.30.1" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", - "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", - "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", - "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", - "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", - "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", - "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", - "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", - "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", - "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", - "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/linebreak": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", - "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", - "license": "MIT", - "dependencies": { - "base64-js": "0.0.8", - "unicode-trie": "^2.0.0" - } - }, - "node_modules/linebreak/node_modules/base64-js": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", - "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/local-pkg": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", - "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mlly": "^1.7.3", - "pkg-types": "^1.2.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, - "node_modules/lodash.castarray": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", - "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/longest-streak": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", - "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/magicast": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" - } - }, - "node_modules/markdown-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", - "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/markdown-table": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", - "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/marked": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", - "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/marked-footnote": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/marked-footnote/-/marked-footnote-1.2.4.tgz", - "integrity": "sha512-DB2Kl+wFh6YwZd70qABMY6WUkG1UuyqoNTFoDfGyG79Pz24neYtLBkB+45a7o72V7gkfvbC3CGzIYFobxfMT1Q==", - "license": "MIT", - "peerDependencies": { - "marked": ">=7.0.0" - } - }, - "node_modules/marked-smartypants": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/marked-smartypants/-/marked-smartypants-1.1.9.tgz", - "integrity": "sha512-VPeuaUr5IWptI7nJdgQ9ugrLWYGv13NdzEXTtKY3cmB4aRWOI2RzhLlf+xQp6Wnob9SAPO2sNVlfSJr+nflk/A==", - "license": "MIT", - "dependencies": { - "smartypants": "^0.2.2" - }, - "peerDependencies": { - "marked": ">=4 <16" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mdast-util-definitions": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", - "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "unist-util-visit": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-find-and-replace": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", - "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "escape-string-regexp": "^5.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-from-markdown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", - "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark": "^4.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", - "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-gfm-autolink-literal": "^2.0.0", - "mdast-util-gfm-footnote": "^2.0.0", - "mdast-util-gfm-strikethrough": "^2.0.0", - "mdast-util-gfm-table": "^2.0.0", - "mdast-util-gfm-task-list-item": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", - "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-find-and-replace": "^3.0.0", - "micromark-util-character": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-strikethrough": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", - "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", - "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "markdown-table": "^3.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-task-list-item": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", - "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", - "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-expression": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", - "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-jsx": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", - "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "parse-entities": "^4.0.0", - "stringify-entities": "^4.0.0", - "unist-util-stringify-position": "^4.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdxjs-esm": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", - "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-phrasing": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", - "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", - "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@ungap/structured-clone": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "trim-lines": "^3.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-markdown": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", - "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^4.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "unist-util-visit": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", - "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromark": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", - "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", - "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-destination": "^2.0.0", - "micromark-factory-label": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-title": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-html-tag-name": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", - "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", - "license": "MIT", - "dependencies": { - "micromark-extension-gfm-autolink-literal": "^2.0.0", - "micromark-extension-gfm-footnote": "^2.0.0", - "micromark-extension-gfm-strikethrough": "^2.0.0", - "micromark-extension-gfm-table": "^2.0.0", - "micromark-extension-gfm-tagfilter": "^2.0.0", - "micromark-extension-gfm-task-list-item": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", - "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", - "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-table": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", - "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", - "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", - "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdx-expression": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", - "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-mdx-expression": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdx-jsx": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", - "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "micromark-factory-mdx-expression": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdx-md": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", - "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdxjs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", - "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", - "license": "MIT", - "dependencies": { - "acorn": "^8.0.0", - "acorn-jsx": "^5.0.0", - "micromark-extension-mdx-expression": "^3.0.0", - "micromark-extension-mdx-jsx": "^3.0.0", - "micromark-extension-mdx-md": "^2.0.0", - "micromark-extension-mdxjs-esm": "^3.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdxjs-esm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", - "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-factory-destination": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", - "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-label": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", - "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-mdx-expression": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", - "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" - } - }, - "node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", - "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", - "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-chunked": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", - "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-classify-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", - "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-combine-extensions": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", - "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", - "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-string": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", - "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-events-to-acorn": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", - "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "estree-util-visit": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "vfile-message": "^4.0.0" - } - }, - "node_modules/micromark-util-html-tag-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", - "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", - "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-resolve-all": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", - "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-subtokenize": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", - "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-types": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz", - "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mini-svg-data-uri": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", - "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", - "dev": true, - "license": "MIT", - "bin": { - "mini-svg-data-uri": "cli.js" - } - }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", - "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" - }, - "node_modules/mlly": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", - "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.14.0", - "pathe": "^2.0.1", - "pkg-types": "^1.3.0", - "ufo": "^1.5.4" - } - }, - "node_modules/mlly/node_modules/pathe": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", - "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", - "dev": true, - "license": "MIT" - }, - "node_modules/mrmime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/muggle-string": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", - "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" - }, - "node_modules/napi-postinstall": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.3.tgz", - "integrity": "sha512-Mi7JISo/4Ij2tDZ2xBE2WH+/KvVlkhA6juEjpEeRAVPNCpN3nxJo/5FhDNKgBcdmcmhaH6JjgST4xY/23ZYK0w==", - "dev": true, - "license": "MIT", - "bin": { - "napi-postinstall": "lib/cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/napi-postinstall" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/neotraverse": { - "version": "0.6.18", - "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", - "integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/nlcst-to-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", - "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==", - "license": "MIT", - "dependencies": { - "@types/nlcst": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/node-abi": { - "version": "3.74.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz", - "integrity": "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "license": "MIT" - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "license": "MIT", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/node-fetch-native": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.6.tgz", - "integrity": "sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==", - "license": "MIT" - }, - "node_modules/node-mock-http": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.0.tgz", - "integrity": "sha512-0uGYQ1WQL1M5kKvGRXWQ3uZCHtLTO8hln3oBjIusM75WoesZ909uQJs/Hb946i2SS+Gsrhkaa6iAO17jRIv6DQ==", - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-to-formdata": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/object-to-formdata/-/object-to-formdata-4.5.1.tgz", - "integrity": "sha512-QiM9D0NiU5jV6J6tjE1g7b4Z2tcUnKs1OPUi4iMb2zH+7jwlcUrASghgkFk9GtzqNNq8rTQJtT8AzjBAvLoNMw==", - "license": "MIT" - }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.groupby": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", - "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.values": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", - "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/ofetch": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.4.1.tgz", - "integrity": "sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==", - "license": "MIT", - "dependencies": { - "destr": "^2.0.3", - "node-fetch-native": "^1.6.4", - "ufo": "^1.5.4" - } - }, - "node_modules/ohash": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", - "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", - "license": "MIT" - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/oniguruma-parser": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.5.4.tgz", - "integrity": "sha512-yNxcQ8sKvURiTwP0mV6bLQCYE7NKfKRRWunhbZnXgxSmB1OXa1lHrN3o4DZd+0Si0kU5blidK7BcROO8qv5TZA==", - "license": "MIT" - }, - "node_modules/oniguruma-to-es": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.1.0.tgz", - "integrity": "sha512-SNwG909cSLo4vPyyPbU/VJkEc9WOXqu2ycBlfd1UCXLqk1IijcQktSBb2yRQ2UFPsDhpkaf+C1dtT3PkLK/yWA==", - "license": "MIT", - "dependencies": { - "emoji-regex-xs": "^1.0.0", - "oniguruma-parser": "^0.5.4", - "regex": "^6.0.1", - "regex-recursion": "^6.0.2" - } - }, - "node_modules/open": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", - "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", - "license": "MIT", - "dependencies": { - "default-browser": "^5.2.1", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "is-wsl": "^3.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/own-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/p-limit": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", - "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==", - "license": "MIT", - "dependencies": { - "yocto-queue": "^1.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate/node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-queue": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.0.tgz", - "integrity": "sha512-mxLDbbGIBEXTJL0zEx8JIylaj3xQ7Z/7eEVjcF9fJX4DBiH9oqe+oahYnlKKxm0Ci9TlWTyhSHgygxMxjIB2jw==", - "license": "MIT", - "dependencies": { - "eventemitter3": "^5.0.1", - "p-timeout": "^6.1.2" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-timeout": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", - "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/package-manager-detector": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.9.tgz", - "integrity": "sha512-+vYvA/Y31l8Zk8dwxHhL3JfTuHPm6tlxM2A3GeQyl7ovYnSp1+mzAxClxaOr0qO1TtPxbQxetI7v5XqKLJZk7Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/pako": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", - "license": "MIT" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-css-color": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/parse-css-color/-/parse-css-color-0.2.1.tgz", - "integrity": "sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==", - "license": "MIT", - "dependencies": { - "color-name": "^1.1.4", - "hex-rgb": "^4.1.0" - } - }, - "node_modules/parse-entities": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", - "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "character-entities-legacy": "^3.0.0", - "character-reference-invalid": "^2.0.0", - "decode-named-character-reference": "^1.0.0", - "is-alphanumerical": "^2.0.0", - "is-decimal": "^2.0.0", - "is-hexadecimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parse-entities/node_modules/@types/unist": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", - "license": "MIT" - }, - "node_modules/parse-latin": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz", - "integrity": "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==", - "license": "MIT", - "dependencies": { - "@types/nlcst": "^2.0.0", - "@types/unist": "^3.0.0", - "nlcst-to-string": "^4.0.0", - "unist-util-modify-children": "^4.0.0", - "unist-util-visit-children": "^3.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parse5": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", - "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", - "license": "MIT", - "dependencies": { - "entities": "^4.5.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", - "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "domhandler": "^5.0.3", - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-parser-stream": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", - "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "license": "MIT" - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true, - "license": "MIT" - }, - "node_modules/pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "license": "ISC", - "optional": true, - "peer": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/pg-protocol": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.8.0.tgz", - "integrity": "sha512-jvuYlEkL03NRvOoyoRktBK7+qU5kOvlAwvmrH8sr3wbLrOdVWsRxQfz8mMy9sZFsqJ1hEWNfdWKI4SAmoL+j7g==", - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "license": "MIT", - "optional": true, - "peer": true, - "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" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/pkg-types/node_modules/pathe": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", - "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", - "dev": true, - "license": "MIT" - }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.8", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", - "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "license": "MIT" - }, - "node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/postgres-bytea": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "xtend": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", - "devOptional": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-plugin-astro": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/prettier-plugin-astro/-/prettier-plugin-astro-0.14.1.tgz", - "integrity": "sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@astrojs/compiler": "^2.9.1", - "prettier": "^3.0.0", - "sass-formatter": "^0.7.6" - }, - "engines": { - "node": "^14.15.0 || >=16.0.0" - } - }, - "node_modules/prettier-plugin-tailwindcss": { - "version": "0.6.11", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.11.tgz", - "integrity": "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.21.3" - }, - "peerDependencies": { - "@ianvs/prettier-plugin-sort-imports": "*", - "@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-import-sort": "*", - "prettier-plugin-jsdoc": "*", - "prettier-plugin-marko": "*", - "prettier-plugin-multiline-arrays": "*", - "prettier-plugin-organize-attributes": "*", - "prettier-plugin-organize-imports": "*", - "prettier-plugin-sort-imports": "*", - "prettier-plugin-style-order": "*", - "prettier-plugin-svelte": "*" - }, - "peerDependenciesMeta": { - "@ianvs/prettier-plugin-sort-imports": { - "optional": true - }, - "@prettier/plugin-pug": { - "optional": true - }, - "@shopify/prettier-plugin-liquid": { - "optional": true - }, - "@trivago/prettier-plugin-sort-imports": { - "optional": true - }, - "@zackad/prettier-plugin-twig": { - "optional": true - }, - "prettier-plugin-astro": { - "optional": true - }, - "prettier-plugin-css-order": { - "optional": true - }, - "prettier-plugin-import-sort": { - "optional": true - }, - "prettier-plugin-jsdoc": { - "optional": true - }, - "prettier-plugin-marko": { - "optional": true - }, - "prettier-plugin-multiline-arrays": { - "optional": true - }, - "prettier-plugin-organize-attributes": { - "optional": true - }, - "prettier-plugin-organize-imports": { - "optional": true - }, - "prettier-plugin-sort-imports": { - "optional": true - }, - "prettier-plugin-style-order": { - "optional": true - }, - "prettier-plugin-svelte": { - "optional": true - } - } - }, - "node_modules/prisma": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.8.2.tgz", - "integrity": "sha512-JNricTXQxzDtRS7lCGGOB4g5DJ91eg3nozdubXze3LpcMl1oWwcFddrj++Up3jnRE6X/3gB/xz3V+ecBk/eEGA==", - "devOptional": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/config": "6.8.2", - "@prisma/engines": "6.8.2" - }, - "bin": { - "prisma": "build/index.js" - }, - "engines": { - "node": ">=18.18" - }, - "peerDependencies": { - "typescript": ">=5.1.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/prisma-json-types-generator": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/prisma-json-types-generator/-/prisma-json-types-generator-3.4.1.tgz", - "integrity": "sha512-VWsuvCyHyOHoyyuishw9hPjoDYx9+sa+v0E0ggHheYkQ5N9tt6EXd9AYaR181WWMG+K+1Ua2tW0yEsl52pe9ew==", - "dev": true, - "license": "MIT", - "dependencies": { - "@prisma/generator-helper": "^6.7.0", - "tslib": "^2.8.1" - }, - "bin": { - "prisma-json-types-generator": "index.js" - }, - "engines": { - "node": ">=14.0" - }, - "funding": { - "url": "https://github.com/arthurfiorette/prisma-json-types-generator?sponsor=1" - }, - "peerDependencies": { - "prisma": "^6.7", - "typescript": "^5.8" - } - }, - "node_modules/prismjs": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", - "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/promise-limit": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz", - "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==", - "license": "ISC" - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prompts/node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/property-information": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", - "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true, - "license": "MIT" - }, - "node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/radix3": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", - "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", - "license": "MIT" - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", - "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/recma-build-jsx": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", - "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-util-build-jsx": "^3.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/recma-jsx": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.0.tgz", - "integrity": "sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q==", - "license": "MIT", - "dependencies": { - "acorn-jsx": "^5.0.0", - "estree-util-to-js": "^2.0.0", - "recma-parse": "^1.0.0", - "recma-stringify": "^1.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/recma-parse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", - "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "esast-util-from-js": "^2.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/recma-stringify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", - "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-util-to-js": "^2.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/redis": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/redis/-/redis-5.0.1.tgz", - "integrity": "sha512-J8nqUjrfSq0E8NQkcHDZ4HdEQk5RMYjP3jZq02PE+ERiRxolbDNxPaTT4xh6tdrme+lJ86Goje9yMt9uzh23hQ==", - "license": "MIT", - "dependencies": { - "@redis/bloom": "5.0.1", - "@redis/client": "5.0.1", - "@redis/json": "5.0.1", - "@redis/search": "5.0.1", - "@redis/time-series": "5.0.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", - "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz", - "integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==", - "license": "MIT", - "dependencies": { - "regex-utilities": "^2.3.0" - } - }, - "node_modules/regex-recursion": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", - "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", - "license": "MIT", - "dependencies": { - "regex-utilities": "^2.3.0" - } - }, - "node_modules/regex-utilities": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", - "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", - "license": "MIT" - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/rehype": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.2.tgz", - "integrity": "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "rehype-parse": "^9.0.0", - "rehype-stringify": "^10.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-parse": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", - "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-from-html": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-raw": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", - "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-raw": "^9.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-recma": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", - "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/hast": "^3.0.0", - "hast-util-to-estree": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-stringify": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", - "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-to-html": "^9.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/relative-time-format": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/relative-time-format/-/relative-time-format-1.1.6.tgz", - "integrity": "sha512-aCv3juQw4hT1/P/OrVltKWLlp15eW1GRcwP1XdxHrPdZE9MtgqFpegjnTjLhi2m2WI9MT/hQQtE+tjEWG1hgkQ==", - "license": "MIT" - }, - "node_modules/remark-gfm": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", - "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-gfm": "^3.0.0", - "micromark-extension-gfm": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-stringify": "^11.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-mdx": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.0.tgz", - "integrity": "sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==", - "license": "MIT", - "dependencies": { - "mdast-util-mdx": "^3.0.0", - "micromark-extension-mdxjs": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-parse": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", - "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-rehype": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", - "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "mdast-util-to-hast": "^13.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-smartypants": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.2.tgz", - "integrity": "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==", - "license": "MIT", - "dependencies": { - "retext": "^9.0.0", - "retext-smartypants": "^6.0.0", - "unified": "^11.0.4", - "unist-util-visit": "^5.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/remark-stringify": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", - "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-to-markdown": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/request-light": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.7.0.tgz", - "integrity": "sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==", - "license": "MIT" - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "devOptional": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/restructure": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz", - "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==", - "license": "MIT" - }, - "node_modules/retext": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", - "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==", - "license": "MIT", - "dependencies": { - "@types/nlcst": "^2.0.0", - "retext-latin": "^4.0.0", - "retext-stringify": "^4.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/retext-latin": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-4.0.0.tgz", - "integrity": "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==", - "license": "MIT", - "dependencies": { - "@types/nlcst": "^2.0.0", - "parse-latin": "^7.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/retext-smartypants": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.2.0.tgz", - "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==", - "license": "MIT", - "dependencies": { - "@types/nlcst": "^2.0.0", - "nlcst-to-string": "^4.0.0", - "unist-util-visit": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/retext-stringify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-4.0.0.tgz", - "integrity": "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==", - "license": "MIT", - "dependencies": { - "@types/nlcst": "^2.0.0", - "nlcst-to-string": "^4.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rollup": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.1.tgz", - "integrity": "sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==", - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.7" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.40.1", - "@rollup/rollup-android-arm64": "4.40.1", - "@rollup/rollup-darwin-arm64": "4.40.1", - "@rollup/rollup-darwin-x64": "4.40.1", - "@rollup/rollup-freebsd-arm64": "4.40.1", - "@rollup/rollup-freebsd-x64": "4.40.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.40.1", - "@rollup/rollup-linux-arm-musleabihf": "4.40.1", - "@rollup/rollup-linux-arm64-gnu": "4.40.1", - "@rollup/rollup-linux-arm64-musl": "4.40.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.40.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.40.1", - "@rollup/rollup-linux-riscv64-gnu": "4.40.1", - "@rollup/rollup-linux-riscv64-musl": "4.40.1", - "@rollup/rollup-linux-s390x-gnu": "4.40.1", - "@rollup/rollup-linux-x64-gnu": "4.40.1", - "@rollup/rollup-linux-x64-musl": "4.40.1", - "@rollup/rollup-win32-arm64-msvc": "4.40.1", - "@rollup/rollup-win32-ia32-msvc": "4.40.1", - "@rollup/rollup-win32-x64-msvc": "4.40.1", - "fsevents": "~2.3.2" - } - }, - "node_modules/run-applescript": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", - "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/s.color": { - "version": "0.0.15", - "resolved": "https://registry.npmjs.org/s.color/-/s.color-0.0.15.tgz", - "integrity": "sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safe-push-apply": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", - "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/sass-formatter": { - "version": "0.7.9", - "resolved": "https://registry.npmjs.org/sass-formatter/-/sass-formatter-0.7.9.tgz", - "integrity": "sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "suf-log": "^2.5.3" - } - }, - "node_modules/satori": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/satori/-/satori-0.12.2.tgz", - "integrity": "sha512-3C/laIeE6UUe9A+iQ0A48ywPVCCMKCNSTU5Os101Vhgsjd3AAxGNjyq0uAA8kulMPK5n0csn8JlxPN9riXEjLA==", - "license": "MPL-2.0", - "dependencies": { - "@shuding/opentype.js": "1.4.0-beta.0", - "css-background-parser": "^0.1.0", - "css-box-shadow": "1.0.0-3", - "css-gradient-parser": "^0.0.16", - "css-to-react-native": "^3.0.0", - "emoji-regex": "^10.2.1", - "escape-html": "^1.0.3", - "linebreak": "^1.1.0", - "parse-css-color": "^0.2.1", - "postcss-value-parser": "^4.2.0", - "yoga-wasm-web": "^0.3.3" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/sax": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", - "license": "ISC" - }, - "node_modules/schema-dts": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/schema-dts/-/schema-dts-1.1.5.tgz", - "integrity": "sha512-RJr9EaCmsLzBX2NDiO5Z3ux2BVosNZN5jo0gWgsyKvxKIUL5R3swNvoorulAeL9kLB0iTSX7V6aokhla2m7xbg==", - "license": "Apache-2.0" - }, - "node_modules/seedrandom": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", - "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/server-destroy": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", - "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==", - "license": "ISC" - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", - "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/sharp": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", - "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", - "hasInstallScript": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.3", - "semver": "^7.6.3" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.5", - "@img/sharp-darwin-x64": "0.33.5", - "@img/sharp-libvips-darwin-arm64": "1.0.4", - "@img/sharp-libvips-darwin-x64": "1.0.4", - "@img/sharp-libvips-linux-arm": "1.0.5", - "@img/sharp-libvips-linux-arm64": "1.0.4", - "@img/sharp-libvips-linux-s390x": "1.0.4", - "@img/sharp-libvips-linux-x64": "1.0.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", - "@img/sharp-libvips-linuxmusl-x64": "1.0.4", - "@img/sharp-linux-arm": "0.33.5", - "@img/sharp-linux-arm64": "0.33.5", - "@img/sharp-linux-s390x": "0.33.5", - "@img/sharp-linux-x64": "0.33.5", - "@img/sharp-linuxmusl-arm64": "0.33.5", - "@img/sharp-linuxmusl-x64": "0.33.5", - "@img/sharp-wasm32": "0.33.5", - "@img/sharp-win32-ia32": "0.33.5", - "@img/sharp-win32-x64": "0.33.5" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/shiki": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.2.1.tgz", - "integrity": "sha512-VML/2o1/KGYkEf/stJJ+s9Ypn7jUKQPomGLGYso4JJFMFxVDyPNsjsI3MB3KLjlMOeH44gyaPdXC6rik2WXvUQ==", - "license": "MIT", - "dependencies": { - "@shikijs/core": "3.2.1", - "@shikijs/engine-javascript": "3.2.1", - "@shikijs/engine-oniguruma": "3.2.1", - "@shikijs/langs": "3.2.1", - "@shikijs/themes": "3.2.1", - "@shikijs/types": "3.2.1", - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "license": "MIT", - "optional": true, - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "license": "MIT" - }, - "node_modules/sitemap": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-8.0.0.tgz", - "integrity": "sha512-+AbdxhM9kJsHtruUF39bwS/B0Fytw6Fr1o4ZAIAEqA6cke2xcoO2GleBw9Zw7nRzILVEgz7zBM5GiTJjie1G9A==", - "license": "MIT", - "dependencies": { - "@types/node": "^17.0.5", - "@types/sax": "^1.2.1", - "arg": "^5.0.0", - "sax": "^1.2.4" - }, - "bin": { - "sitemap": "dist/cli.js" - }, - "engines": { - "node": ">=14.0.0", - "npm": ">=6.0.0" - } - }, - "node_modules/sitemap/node_modules/@types/node": { - "version": "17.0.45", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", - "license": "MIT" - }, - "node_modules/slugify": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", - "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==", - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/smartypants": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/smartypants/-/smartypants-0.2.2.tgz", - "integrity": "sha512-TzobUYoEft/xBtb2voRPryAUIvYguG0V7Tt3de79I1WfXgCwelqVsGuZSnu3GFGRZhXR90AeEYIM+icuB/S06Q==", - "license": "BSD-3-Clause", - "bin": { - "smartypants": "bin/smartypants.js", - "smartypantsu": "bin/smartypantsu.js" - } - }, - "node_modules/smol-toml": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.3.1.tgz", - "integrity": "sha512-tEYNll18pPKHroYSmLLrksq233j021G0giwW7P3D24jC54pQ5W5BXMsQ/Mvw1OJCmEYDgY+lrzT+3nNUtoNfXQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">= 18" - }, - "funding": { - "url": "https://github.com/sponsors/cyyynthia" - } - }, - "node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">= 8" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/space-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/stable-hash": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", - "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", - "dev": true, - "license": "MIT" - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/stream-replace-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/stream-replace-string/-/stream-replace-string-2.0.0.tgz", - "integrity": "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==", - "license": "MIT" - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string.prototype.codepointat": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", - "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==", - "license": "MIT" - }, - "node_modules/string.prototype.includes": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", - "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/stringify-entities": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", - "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", - "license": "MIT", - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/style-to-js": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz", - "integrity": "sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==", - "license": "MIT", - "dependencies": { - "style-to-object": "1.0.8" - } - }, - "node_modules/style-to-object": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", - "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", - "license": "MIT", - "dependencies": { - "inline-style-parser": "0.2.4" - } - }, - "node_modules/suf-log": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/suf-log/-/suf-log-2.5.3.tgz", - "integrity": "sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "s.color": "0.0.15" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/svgo": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", - "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^5.1.0", - "css-tree": "^2.3.1", - "css-what": "^6.1.0", - "csso": "^5.0.5", - "picocolors": "^1.0.0" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/svgo" - } - }, - "node_modules/svgo/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/synckit": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", - "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, - "node_modules/tailwind-htmx": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/tailwind-htmx/-/tailwind-htmx-0.1.2.tgz", - "integrity": "sha512-EJhlT7PWARTucn8mOZ3P5Jnk7GaaY6bD/p3YSg5AumXEXhGcKHP7FpRysjpgjFdY1Y4S5+taBCkWIDJwW7ffIw==", - "dev": true - }, - "node_modules/tailwind-merge": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.0.tgz", - "integrity": "sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/dcastil" - } - }, - "node_modules/tailwind-variants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-1.0.0.tgz", - "integrity": "sha512-2WSbv4ulEEyuBKomOunut65D8UZwxrHoRfYnxGcQNnHqlSCp2+B7Yz2W+yrNDrxRodOXtGD/1oCcKGNBnUqMqA==", - "license": "MIT", - "dependencies": { - "tailwind-merge": "3.0.2" - }, - "engines": { - "node": ">=16.x", - "pnpm": ">=7.x" - }, - "peerDependencies": { - "tailwindcss": "*" - } - }, - "node_modules/tailwind-variants/node_modules/tailwind-merge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.2.tgz", - "integrity": "sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/dcastil" - } - }, - "node_modules/tailwindcss": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.7.tgz", - "integrity": "sha512-kr1o/ErIdNhTz8uzAYL7TpaUuzKIE6QPQ4qmSdxnoX/lo+5wmUHQA6h3L5yIqEImSRnAAURDirLu/BgiXGPAhg==", - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "dev": true, - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tar-fs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz", - "integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==", - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-fs/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/tiny-inflate": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", - "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", - "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", - "license": "MIT", - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/trim-lines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/trough": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", - "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/ts-essentials": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-10.0.4.tgz", - "integrity": "sha512-lwYdz28+S4nicm+jFi6V58LaAIpxzhg9rLdgNC1VsdP/xiFBseGhF1M/shwCk6zMmwahBZdXcl34LVHrEang3A==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "typescript": ">=4.5.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/ts-toolbelt": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz", - "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/tsconfck": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.5.tgz", - "integrity": "sha512-CLDfGgUp7XPswWnezWwsCRxNmgQjhYq3VXHM0/XIRxhVrKw0M1if9agzryh1QS3nxjCROvV+xWxoJO1YctzzWg==", - "license": "MIT", - "bin": { - "tsconfck": "bin/tsconfck.js" - }, - "engines": { - "node": "^18 || >=20" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/tsconfig-paths": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/tsx": { - "version": "4.19.4", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.4.tgz", - "integrity": "sha512-gK5GVzDkJK1SI1zwHf32Mqxf2tSJkNx+eYcNly5+nHvWqXUJYUkWBQtKauoESz3ymezAI++ZwT855x5p5eop+Q==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.25.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "4.32.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.32.0.tgz", - "integrity": "sha512-rfgpoi08xagF3JSdtJlCwMq9DGNDE0IMh3Mkpc1wUypg9vPi786AiqeBBKcqvIkq42azsBM85N490fyZjeUftw==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", - "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typesafe-path": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/typesafe-path/-/typesafe-path-0.2.2.tgz", - "integrity": "sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==", - "license": "MIT" - }, - "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-auto-import-cache": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/typescript-auto-import-cache/-/typescript-auto-import-cache-0.3.5.tgz", - "integrity": "sha512-fAIveQKsoYj55CozUiBoj4b/7WpN0i4o74wiGY5JVUEoD0XiqDk1tJqTEjgzL2/AizKQrXxyRosSebyDzBZKjw==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.8" - } - }, - "node_modules/typescript-eslint": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.32.1.tgz", - "integrity": "sha512-D7el+eaDHAmXvrZBy1zpzSNIRqnCOrkwTgZxTu3MUqRWk8k0q9m9Ho4+vPf7iHtgUfrK/o8IZaEApsxPlHTFCg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.32.1", - "@typescript-eslint/parser": "8.32.1", - "@typescript-eslint/utils": "8.32.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/ufo": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", - "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", - "license": "MIT" - }, - "node_modules/ultrahtml": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.6.0.tgz", - "integrity": "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==", - "license": "MIT" - }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/uncrypto": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", - "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", - "license": "MIT" - }, - "node_modules/undici": { - "version": "6.21.3", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", - "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.17" - } - }, - "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "license": "MIT" - }, - "node_modules/unicode-properties": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", - "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.0", - "unicode-trie": "^2.0.0" - } - }, - "node_modules/unicode-trie": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", - "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", - "license": "MIT", - "dependencies": { - "pako": "^0.2.5", - "tiny-inflate": "^1.0.0" - } - }, - "node_modules/unified": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", - "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "bail": "^2.0.0", - "devlop": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unifont": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.5.0.tgz", - "integrity": "sha512-4DueXMP5Hy4n607sh+vJ+rajoLu778aU3GzqeTCqsD/EaUcvqZT9wPC8kgK6Vjh22ZskrxyRCR71FwNOaYn6jA==", - "license": "MIT", - "dependencies": { - "css-tree": "^3.0.0", - "ohash": "^2.0.0" - } - }, - "node_modules/unifont/node_modules/css-tree": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", - "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", - "license": "MIT", - "dependencies": { - "mdn-data": "2.12.2", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/unifont/node_modules/mdn-data": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", - "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", - "license": "CC0-1.0" - }, - "node_modules/unique-username-generator": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/unique-username-generator/-/unique-username-generator-1.4.0.tgz", - "integrity": "sha512-Y6CY5vLPeixB5V4t6b/y7KkOPWpbANCIln4O6MRzz2uPIxzDm6KshWxJKMeAxLU2EPyCK+ptw/YeZMkZPFv2bw==", - "license": "MIT" - }, - "node_modules/unist-util-find-after": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", - "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-is": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", - "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-modify-children": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz", - "integrity": "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "array-iterate": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position-from-estree": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", - "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-remove-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", - "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-visit": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-children": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz", - "integrity": "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", - "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unrs-resolver": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.7.2.tgz", - "integrity": "sha512-BBKpaylOW8KbHsu378Zky/dGh4ckT/4NW/0SHRABdqRLcQJ2dAOjDo9g97p04sWflm0kqPqpUatxReNV/dqI5A==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "napi-postinstall": "^0.2.2" - }, - "funding": { - "url": "https://github.com/sponsors/JounQin" - }, - "optionalDependencies": { - "@unrs/resolver-binding-darwin-arm64": "1.7.2", - "@unrs/resolver-binding-darwin-x64": "1.7.2", - "@unrs/resolver-binding-freebsd-x64": "1.7.2", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.7.2", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.7.2", - "@unrs/resolver-binding-linux-arm64-gnu": "1.7.2", - "@unrs/resolver-binding-linux-arm64-musl": "1.7.2", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.7.2", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.7.2", - "@unrs/resolver-binding-linux-riscv64-musl": "1.7.2", - "@unrs/resolver-binding-linux-s390x-gnu": "1.7.2", - "@unrs/resolver-binding-linux-x64-gnu": "1.7.2", - "@unrs/resolver-binding-linux-x64-musl": "1.7.2", - "@unrs/resolver-binding-wasm32-wasi": "1.7.2", - "@unrs/resolver-binding-win32-arm64-msvc": "1.7.2", - "@unrs/resolver-binding-win32-ia32-msvc": "1.7.2", - "@unrs/resolver-binding-win32-x64-msvc": "1.7.2" - } - }, - "node_modules/unstorage": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.15.0.tgz", - "integrity": "sha512-m40eHdGY/gA6xAPqo8eaxqXgBuzQTlAKfmB1iF7oCKXE1HfwHwzDJBywK+qQGn52dta+bPlZluPF7++yR3p/bg==", - "license": "MIT", - "dependencies": { - "anymatch": "^3.1.3", - "chokidar": "^4.0.3", - "destr": "^2.0.3", - "h3": "^1.15.0", - "lru-cache": "^10.4.3", - "node-fetch-native": "^1.6.6", - "ofetch": "^1.4.1", - "ufo": "^1.5.4" - }, - "peerDependencies": { - "@azure/app-configuration": "^1.8.0", - "@azure/cosmos": "^4.2.0", - "@azure/data-tables": "^13.3.0", - "@azure/identity": "^4.6.0", - "@azure/keyvault-secrets": "^4.9.0", - "@azure/storage-blob": "^12.26.0", - "@capacitor/preferences": "^6.0.3", - "@deno/kv": ">=0.9.0", - "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0", - "@planetscale/database": "^1.19.0", - "@upstash/redis": "^1.34.3", - "@vercel/blob": ">=0.27.1", - "@vercel/kv": "^1.0.1", - "aws4fetch": "^1.0.20", - "db0": ">=0.2.1", - "idb-keyval": "^6.2.1", - "ioredis": "^5.4.2", - "uploadthing": "^7.4.4" - }, - "peerDependenciesMeta": { - "@azure/app-configuration": { - "optional": true - }, - "@azure/cosmos": { - "optional": true - }, - "@azure/data-tables": { - "optional": true - }, - "@azure/identity": { - "optional": true - }, - "@azure/keyvault-secrets": { - "optional": true - }, - "@azure/storage-blob": { - "optional": true - }, - "@capacitor/preferences": { - "optional": true - }, - "@deno/kv": { - "optional": true - }, - "@netlify/blobs": { - "optional": true - }, - "@planetscale/database": { - "optional": true - }, - "@upstash/redis": { - "optional": true - }, - "@vercel/blob": { - "optional": true - }, - "@vercel/kv": { - "optional": true - }, - "aws4fetch": { - "optional": true - }, - "db0": { - "optional": true - }, - "idb-keyval": { - "optional": true - }, - "ioredis": { - "optional": true - }, - "uploadthing": { - "optional": true - } - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-location": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", - "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", - "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vite": { - "version": "6.3.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.4.tgz", - "integrity": "sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==", - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vitefu": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.6.tgz", - "integrity": "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==", - "license": "MIT", - "workspaces": [ - "tests/deps/*", - "tests/projects/*" - ], - "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, - "node_modules/volar-service-css": { - "version": "0.0.62", - "resolved": "https://registry.npmjs.org/volar-service-css/-/volar-service-css-0.0.62.tgz", - "integrity": "sha512-JwNyKsH3F8PuzZYuqPf+2e+4CTU8YoyUHEHVnoXNlrLe7wy9U3biomZ56llN69Ris7TTy/+DEX41yVxQpM4qvg==", - "license": "MIT", - "dependencies": { - "vscode-css-languageservice": "^6.3.0", - "vscode-languageserver-textdocument": "^1.0.11", - "vscode-uri": "^3.0.8" - }, - "peerDependencies": { - "@volar/language-service": "~2.4.0" - }, - "peerDependenciesMeta": { - "@volar/language-service": { - "optional": true - } - } - }, - "node_modules/volar-service-emmet": { - "version": "0.0.62", - "resolved": "https://registry.npmjs.org/volar-service-emmet/-/volar-service-emmet-0.0.62.tgz", - "integrity": "sha512-U4dxWDBWz7Pi4plpbXf4J4Z/ss6kBO3TYrACxWNsE29abu75QzVS0paxDDhI6bhqpbDFXlpsDhZ9aXVFpnfGRQ==", - "license": "MIT", - "dependencies": { - "@emmetio/css-parser": "^0.4.0", - "@emmetio/html-matcher": "^1.3.0", - "@vscode/emmet-helper": "^2.9.3", - "vscode-uri": "^3.0.8" - }, - "peerDependencies": { - "@volar/language-service": "~2.4.0" - }, - "peerDependenciesMeta": { - "@volar/language-service": { - "optional": true - } - } - }, - "node_modules/volar-service-html": { - "version": "0.0.62", - "resolved": "https://registry.npmjs.org/volar-service-html/-/volar-service-html-0.0.62.tgz", - "integrity": "sha512-Zw01aJsZRh4GTGUjveyfEzEqpULQUdQH79KNEiKVYHZyuGtdBRYCHlrus1sueSNMxwwkuF5WnOHfvBzafs8yyQ==", - "license": "MIT", - "dependencies": { - "vscode-html-languageservice": "^5.3.0", - "vscode-languageserver-textdocument": "^1.0.11", - "vscode-uri": "^3.0.8" - }, - "peerDependencies": { - "@volar/language-service": "~2.4.0" - }, - "peerDependenciesMeta": { - "@volar/language-service": { - "optional": true - } - } - }, - "node_modules/volar-service-prettier": { - "version": "0.0.62", - "resolved": "https://registry.npmjs.org/volar-service-prettier/-/volar-service-prettier-0.0.62.tgz", - "integrity": "sha512-h2yk1RqRTE+vkYZaI9KYuwpDfOQRrTEMvoHol0yW4GFKc75wWQRrb5n/5abDrzMPrkQbSip8JH2AXbvrRtYh4w==", - "license": "MIT", - "dependencies": { - "vscode-uri": "^3.0.8" - }, - "peerDependencies": { - "@volar/language-service": "~2.4.0", - "prettier": "^2.2 || ^3.0" - }, - "peerDependenciesMeta": { - "@volar/language-service": { - "optional": true - }, - "prettier": { - "optional": true - } - } - }, - "node_modules/volar-service-typescript": { - "version": "0.0.62", - "resolved": "https://registry.npmjs.org/volar-service-typescript/-/volar-service-typescript-0.0.62.tgz", - "integrity": "sha512-p7MPi71q7KOsH0eAbZwPBiKPp9B2+qrdHAd6VY5oTo9BUXatsOAdakTm9Yf0DUj6uWBAaOT01BSeVOPwucMV1g==", - "license": "MIT", - "dependencies": { - "path-browserify": "^1.0.1", - "semver": "^7.6.2", - "typescript-auto-import-cache": "^0.3.3", - "vscode-languageserver-textdocument": "^1.0.11", - "vscode-nls": "^5.2.0", - "vscode-uri": "^3.0.8" - }, - "peerDependencies": { - "@volar/language-service": "~2.4.0" - }, - "peerDependenciesMeta": { - "@volar/language-service": { - "optional": true - } - } - }, - "node_modules/volar-service-typescript-twoslash-queries": { - "version": "0.0.62", - "resolved": "https://registry.npmjs.org/volar-service-typescript-twoslash-queries/-/volar-service-typescript-twoslash-queries-0.0.62.tgz", - "integrity": "sha512-KxFt4zydyJYYI0kFAcWPTh4u0Ha36TASPZkAnNY784GtgajerUqM80nX/W1d0wVhmcOFfAxkVsf/Ed+tiYU7ng==", - "license": "MIT", - "dependencies": { - "vscode-uri": "^3.0.8" - }, - "peerDependencies": { - "@volar/language-service": "~2.4.0" - }, - "peerDependenciesMeta": { - "@volar/language-service": { - "optional": true - } - } - }, - "node_modules/volar-service-yaml": { - "version": "0.0.62", - "resolved": "https://registry.npmjs.org/volar-service-yaml/-/volar-service-yaml-0.0.62.tgz", - "integrity": "sha512-k7gvv7sk3wa+nGll3MaSKyjwQsJjIGCHFjVkl3wjaSP2nouKyn9aokGmqjrl39mi88Oy49giog2GkZH526wjig==", - "license": "MIT", - "dependencies": { - "vscode-uri": "^3.0.8", - "yaml-language-server": "~1.15.0" - }, - "peerDependencies": { - "@volar/language-service": "~2.4.0" - }, - "peerDependenciesMeta": { - "@volar/language-service": { - "optional": true - } - } - }, - "node_modules/vscode-css-languageservice": { - "version": "6.3.4", - "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-6.3.4.tgz", - "integrity": "sha512-qutdhFg4hnlf6IsOynwtfsN8W0Xc7g3SZd+KK9F2moUEjHtkcZoj5p8uH7BSwHx9hSEXjwKgSRRyHTXThfwAkQ==", - "license": "MIT", - "dependencies": { - "@vscode/l10n": "^0.0.18", - "vscode-languageserver-textdocument": "^1.0.12", - "vscode-languageserver-types": "3.17.5", - "vscode-uri": "^3.1.0" - } - }, - "node_modules/vscode-html-languageservice": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-5.3.3.tgz", - "integrity": "sha512-AK/jJM0VIWRrlfqkDBMZxNMnxYT5I2uoMVRoNJ5ePSplnSaT9mbYjqJlxxeLvUrOW7MEH0vVIDzU48u44QZE0w==", - "license": "MIT", - "dependencies": { - "@vscode/l10n": "^0.0.18", - "vscode-languageserver-textdocument": "^1.0.12", - "vscode-languageserver-types": "^3.17.5", - "vscode-uri": "^3.1.0" - } - }, - "node_modules/vscode-json-languageservice": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-4.1.8.tgz", - "integrity": "sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg==", - "license": "MIT", - "dependencies": { - "jsonc-parser": "^3.0.0", - "vscode-languageserver-textdocument": "^1.0.1", - "vscode-languageserver-types": "^3.16.0", - "vscode-nls": "^5.0.0", - "vscode-uri": "^3.0.2" - }, - "engines": { - "npm": ">=7.0.0" - } - }, - "node_modules/vscode-json-languageservice/node_modules/jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "license": "MIT" - }, - "node_modules/vscode-jsonrpc": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", - "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/vscode-languageserver": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", - "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", - "license": "MIT", - "dependencies": { - "vscode-languageserver-protocol": "3.17.5" - }, - "bin": { - "installServerIntoExtension": "bin/installServerIntoExtension" - } - }, - "node_modules/vscode-languageserver-protocol": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", - "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", - "license": "MIT", - "dependencies": { - "vscode-jsonrpc": "8.2.0", - "vscode-languageserver-types": "3.17.5" - } - }, - "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", - "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", - "license": "MIT" - }, - "node_modules/vscode-languageserver-types": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", - "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", - "license": "MIT" - }, - "node_modules/vscode-nls": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-5.2.0.tgz", - "integrity": "sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==", - "license": "MIT" - }, - "node_modules/vscode-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", - "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", - "license": "MIT" - }, - "node_modules/web-namespaces": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", - "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", - "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-pm-runs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", - "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", - "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/widest-line": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", - "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", - "license": "MIT", - "dependencies": { - "string-width": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", - "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/xxhash-wasm": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", - "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", - "license": "MIT" - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/yaml": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", - "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/yaml-language-server": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/yaml-language-server/-/yaml-language-server-1.15.0.tgz", - "integrity": "sha512-N47AqBDCMQmh6mBLmI6oqxryHRzi33aPFPsJhYy3VTUGCdLHYjGh4FZzpUjRlphaADBBkDmnkM/++KNIOHi5Rw==", - "license": "MIT", - "dependencies": { - "ajv": "^8.11.0", - "lodash": "4.17.21", - "request-light": "^0.5.7", - "vscode-json-languageservice": "4.1.8", - "vscode-languageserver": "^7.0.0", - "vscode-languageserver-textdocument": "^1.0.1", - "vscode-languageserver-types": "^3.16.0", - "vscode-nls": "^5.0.0", - "vscode-uri": "^3.0.2", - "yaml": "2.2.2" - }, - "bin": { - "yaml-language-server": "bin/yaml-language-server" - }, - "optionalDependencies": { - "prettier": "2.8.7" - } - }, - "node_modules/yaml-language-server/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/yaml-language-server/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/yaml-language-server/node_modules/prettier": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz", - "integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==", - "license": "MIT", - "optional": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/yaml-language-server/node_modules/request-light": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.5.8.tgz", - "integrity": "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==", - "license": "MIT" - }, - "node_modules/yaml-language-server/node_modules/vscode-jsonrpc": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz", - "integrity": "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==", - "license": "MIT", - "engines": { - "node": ">=8.0.0 || >=10.0.0" - } - }, - "node_modules/yaml-language-server/node_modules/vscode-languageserver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz", - "integrity": "sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==", - "license": "MIT", - "dependencies": { - "vscode-languageserver-protocol": "3.16.0" - }, - "bin": { - "installServerIntoExtension": "bin/installServerIntoExtension" - } - }, - "node_modules/yaml-language-server/node_modules/vscode-languageserver-protocol": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz", - "integrity": "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==", - "license": "MIT", - "dependencies": { - "vscode-jsonrpc": "6.0.0", - "vscode-languageserver-types": "3.16.0" - } - }, - "node_modules/yaml-language-server/node_modules/vscode-languageserver-types": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", - "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==", - "license": "MIT" - }, - "node_modules/yaml-language-server/node_modules/yaml": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", - "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", - "license": "ISC", - "engines": { - "node": ">= 14" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yocto-queue": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", - "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yocto-spinner": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-0.2.1.tgz", - "integrity": "sha512-lHHxjh0bXaLgdJy3cNnVb/F9myx3CkhrvSOEVTkaUgNMXnYFa2xYPVhtGnqhh3jErY2gParBOHallCbc7NrlZQ==", - "license": "MIT", - "dependencies": { - "yoctocolors": "^2.1.1" - }, - "engines": { - "node": ">=18.19" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yoctocolors": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", - "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yoga-wasm-web": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz", - "integrity": "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==", - "license": "MIT" - }, - "node_modules/zod": { - "version": "3.24.2", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", - "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-form-data": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/zod-form-data/-/zod-form-data-2.0.7.tgz", - "integrity": "sha512-O27uzKMx7qc7z51KXER326Fp966jqHGvZX3i18CbvElF/QqVsQQN6Q7BnzepkzeBzTJnU3golibVSagf4dp7RQ==", - "license": "MIT", - "dependencies": { - "@rvf/set-get": "^7.0.0" - }, - "peerDependencies": { - "zod": ">= 3.11.0" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.24.5", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", - "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.24.1" - } - }, - "node_modules/zod-to-ts": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/zod-to-ts/-/zod-to-ts-1.2.0.tgz", - "integrity": "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==", - "peerDependencies": { - "typescript": "^4.9.4 || ^5.0.2", - "zod": "^3" - } - }, - "node_modules/zwitch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - } - } -} diff --git a/web/package.json b/web/package.json deleted file mode 100644 index fe20ef2..0000000 --- a/web/package.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "name": "kycnot.me", - "type": "module", - "version": "0.0.1", - "scripts": { - "dev": "astro dev", - "build": "astro build --remote", - "preview": "astro preview", - "astro": "astro", - "db-admin": "prisma studio --browser=none", - "db-gen": "prisma generate", - "db-push": "prisma migrate dev", - "db-triggers": "just import-triggers", - "db-update": "prisma migrate dev && just import-triggers", - "db-reset": "prisma migrate reset && prisma migrate dev && just import-triggers && tsx scripts/faker.ts", - "db-fill": "tsx scripts/faker.ts", - "db-fill-clean": "tsx scripts/faker.ts --cleanup", - "format": "prettier --write .", - "lint": "eslint .", - "lint-fix": "eslint . --fix && prettier --write ." - }, - "dependencies": { - "@astrojs/check": "0.9.4", - "@astrojs/db": "0.14.14", - "@astrojs/mdx": "4.2.6", - "@astrojs/node": "9.2.1", - "@astrojs/sitemap": "3.4.0", - "@fontsource-variable/space-grotesk": "5.2.7", - "@fontsource/inter": "5.2.5", - "@prisma/client": "6.8.2", - "@tailwindcss/vite": "4.1.7", - "@types/mime-types": "2.1.4", - "@vercel/og": "0.6.8", - "astro": "5.7.13", - "astro-loading-indicator": "0.7.0", - "astro-remote": "0.3.4", - "astro-seo-schema": "5.0.0", - "canvas": "3.1.0", - "clsx": "2.1.1", - "htmx.org": "1.9.12", - "javascript-time-ago": "2.5.11", - "libphonenumber-js": "1.12.8", - "lodash-es": "4.17.21", - "mime-types": "3.0.1", - "object-to-formdata": "4.5.1", - "react": "19.1.0", - "redis": "5.0.1", - "schema-dts": "1.1.5", - "seedrandom": "3.0.5", - "slugify": "1.6.6", - "tailwind-merge": "3.3.0", - "tailwind-variants": "1.0.0", - "tailwindcss": "4.1.7", - "typescript": "5.8.3", - "unique-username-generator": "1.4.0", - "zod-form-data": "2.0.7" - }, - "devDependencies": { - "@eslint/js": "9.27.0", - "@faker-js/faker": "9.8.0", - "@iconify-json/material-symbols": "1.2.21", - "@iconify-json/mdi": "1.2.3", - "@iconify-json/ri": "1.2.5", - "@stylistic/eslint-plugin": "4.2.0", - "@tailwindcss/forms": "0.5.10", - "@tailwindcss/typography": "0.5.16", - "@types/eslint__js": "9.14.0", - "@types/lodash-es": "4.17.12", - "@types/react": "19.1.4", - "@types/seedrandom": "3.0.8", - "@typescript-eslint/parser": "8.32.1", - "astro-icon": "1.1.5", - "date-fns": "4.1.0", - "eslint": "9.27.0", - "eslint-import-resolver-typescript": "4.3.5", - "eslint-plugin-astro": "1.3.1", - "eslint-plugin-import": "2.31.0", - "eslint-plugin-jsx-a11y": "6.10.2", - "globals": "16.1.0", - "prettier": "3.5.3", - "prettier-plugin-astro": "0.14.1", - "prettier-plugin-tailwindcss": "0.6.11", - "prisma": "6.8.2", - "prisma-json-types-generator": "3.4.1", - "tailwind-htmx": "0.1.2", - "ts-essentials": "10.0.4", - "ts-toolbelt": "9.6.0", - "tsx": "4.19.4", - "typescript-eslint": "8.32.1" - } -} diff --git a/web/prisma/migrations/20250518085822_initial/migration.sql b/web/prisma/migrations/20250518085822_initial/migration.sql deleted file mode 100644 index df254e8..0000000 --- a/web/prisma/migrations/20250518085822_initial/migration.sql +++ /dev/null @@ -1,798 +0,0 @@ --- CreateEnum -CREATE TYPE "CommentStatus" AS ENUM ('PENDING', 'HUMAN_PENDING', 'APPROVED', 'VERIFIED', 'REJECTED'); - --- CreateEnum -CREATE TYPE "OrderIdStatus" AS ENUM ('PENDING', 'APPROVED', 'REJECTED'); - --- CreateEnum -CREATE TYPE "VerificationStatus" AS ENUM ('COMMUNITY_CONTRIBUTED', 'APPROVED', 'VERIFICATION_SUCCESS', 'VERIFICATION_FAILED'); - --- CreateEnum -CREATE TYPE "ServiceInfoBanner" AS ENUM ('NONE', 'NO_LONGER_OPERATIONAL'); - --- CreateEnum -CREATE TYPE "ServiceVisibility" AS ENUM ('PUBLIC', 'UNLISTED', 'HIDDEN'); - --- CreateEnum -CREATE TYPE "Currency" AS ENUM ('MONERO', 'BITCOIN', 'LIGHTNING', 'FIAT', 'CASH'); - --- CreateEnum -CREATE TYPE "EventType" AS ENUM ('WARNING', 'WARNING_SOLVED', 'ALERT', 'ALERT_SOLVED', 'INFO', 'NORMAL', 'UPDATE'); - --- CreateEnum -CREATE TYPE "ServiceUserRole" AS ENUM ('OWNER', 'ADMIN', 'MODERATOR', 'SUPPORT', 'TEAM_MEMBER'); - --- CreateEnum -CREATE TYPE "AccountStatusChange" AS ENUM ('ADMIN_TRUE', 'ADMIN_FALSE', 'VERIFIED_TRUE', 'VERIFIED_FALSE', 'VERIFIER_TRUE', 'VERIFIER_FALSE', 'SPAMMER_TRUE', 'SPAMMER_FALSE'); - --- CreateEnum -CREATE TYPE "NotificationType" AS ENUM ('COMMENT_STATUS_CHANGE', 'REPLY_COMMENT_CREATED', 'COMMUNITY_NOTE_ADDED', 'ROOT_COMMENT_CREATED', 'SUGGESTION_MESSAGE', 'SUGGESTION_STATUS_CHANGE', 'ACCOUNT_STATUS_CHANGE', 'EVENT_CREATED', 'SERVICE_VERIFICATION_STATUS_CHANGE'); - --- CreateEnum -CREATE TYPE "CommentStatusChange" AS ENUM ('MARKED_AS_SPAM', 'UNMARKED_AS_SPAM', 'MARKED_FOR_ADMIN_REVIEW', 'UNMARKED_FOR_ADMIN_REVIEW', 'STATUS_CHANGED_TO_APPROVED', 'STATUS_CHANGED_TO_VERIFIED', 'STATUS_CHANGED_TO_REJECTED', 'STATUS_CHANGED_TO_PENDING'); - --- CreateEnum -CREATE TYPE "ServiceVerificationStatusChange" AS ENUM ('STATUS_CHANGED_TO_COMMUNITY_CONTRIBUTED', 'STATUS_CHANGED_TO_APPROVED', 'STATUS_CHANGED_TO_VERIFICATION_SUCCESS', 'STATUS_CHANGED_TO_VERIFICATION_FAILED'); - --- CreateEnum -CREATE TYPE "ServiceSuggestionStatusChange" AS ENUM ('STATUS_CHANGED_TO_PENDING', 'STATUS_CHANGED_TO_APPROVED', 'STATUS_CHANGED_TO_REJECTED', 'STATUS_CHANGED_TO_WITHDRAWN'); - --- CreateEnum -CREATE TYPE "ServiceSuggestionStatus" AS ENUM ('PENDING', 'APPROVED', 'REJECTED', 'WITHDRAWN'); - --- CreateEnum -CREATE TYPE "ServiceSuggestionType" AS ENUM ('CREATE_SERVICE', 'EDIT_SERVICE'); - --- CreateEnum -CREATE TYPE "AttributeCategory" AS ENUM ('PRIVACY', 'TRUST'); - --- CreateEnum -CREATE TYPE "AttributeType" AS ENUM ('GOOD', 'BAD', 'WARNING', 'INFO'); - --- CreateEnum -CREATE TYPE "VerificationStepStatus" AS ENUM ('PENDING', 'IN_PROGRESS', 'PASSED', 'FAILED'); - --- CreateTable -CREATE TABLE "Comment" ( - "id" SERIAL NOT NULL, - "upvotes" INTEGER NOT NULL DEFAULT 0, - "status" "CommentStatus" NOT NULL DEFAULT 'PENDING', - "suspicious" BOOLEAN NOT NULL DEFAULT false, - "requiresAdminReview" BOOLEAN NOT NULL DEFAULT false, - "communityNote" TEXT, - "verificationNote" TEXT, - "internalNote" TEXT, - "privateContext" TEXT, - "orderId" VARCHAR(100), - "orderIdStatus" "OrderIdStatus" DEFAULT 'PENDING', - "kycRequested" BOOLEAN NOT NULL DEFAULT false, - "fundsBlocked" BOOLEAN NOT NULL DEFAULT false, - "content" TEXT NOT NULL, - "rating" SMALLINT, - "ratingActive" BOOLEAN NOT NULL DEFAULT false, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "authorId" INTEGER NOT NULL, - "serviceId" INTEGER NOT NULL, - "parentId" INTEGER, - - CONSTRAINT "Comment_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Notification" ( - "id" SERIAL NOT NULL, - "userId" INTEGER NOT NULL, - "type" "NotificationType" NOT NULL, - "read" BOOLEAN NOT NULL DEFAULT false, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "aboutCommentId" INTEGER, - "aboutEventId" INTEGER, - "aboutServiceId" INTEGER, - "aboutServiceSuggestionId" INTEGER, - "aboutServiceSuggestionMessageId" INTEGER, - "aboutAccountStatusChange" "AccountStatusChange", - "aboutCommentStatusChange" "CommentStatusChange", - "aboutServiceVerificationStatusChange" "ServiceVerificationStatusChange", - "aboutSuggestionStatusChange" "ServiceSuggestionStatusChange", - - CONSTRAINT "Notification_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "NotificationPreferences" ( - "id" SERIAL NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "userId" INTEGER NOT NULL, - "enableOnMyCommentStatusChange" BOOLEAN NOT NULL DEFAULT true, - "enableAutowatchMyComments" BOOLEAN NOT NULL DEFAULT true, - "enableNotifyPendingRepliesOnWatch" BOOLEAN NOT NULL DEFAULT false, - - CONSTRAINT "NotificationPreferences_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "NotificationPreferenceOnServiceVerificationChangeFilterFilter" ( - "id" SERIAL NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "verificationStatus" "VerificationStepStatus" NOT NULL, - "notificationPreferencesId" INTEGER NOT NULL, - "currencies" "Currency"[], - "scores" INTEGER[], - - CONSTRAINT "NotificationPreferenceOnServiceVerificationChangeFilterFil_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Event" ( - "id" SERIAL NOT NULL, - "title" TEXT NOT NULL, - "content" TEXT NOT NULL, - "source" TEXT, - "type" "EventType" NOT NULL, - "visible" BOOLEAN NOT NULL DEFAULT true, - "startedAt" TIMESTAMP(3) NOT NULL, - "endedAt" TIMESTAMP(3), - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "serviceId" INTEGER NOT NULL, - - CONSTRAINT "Event_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "ServiceSuggestion" ( - "id" SERIAL NOT NULL, - "type" "ServiceSuggestionType" NOT NULL, - "status" "ServiceSuggestionStatus" NOT NULL DEFAULT 'PENDING', - "notes" TEXT, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "userId" INTEGER NOT NULL, - "serviceId" INTEGER NOT NULL, - - CONSTRAINT "ServiceSuggestion_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "ServiceSuggestionMessage" ( - "id" SERIAL NOT NULL, - "content" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "userId" INTEGER NOT NULL, - "suggestionId" INTEGER NOT NULL, - - CONSTRAINT "ServiceSuggestionMessage_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Service" ( - "id" SERIAL NOT NULL, - "name" TEXT NOT NULL, - "slug" TEXT NOT NULL, - "description" TEXT NOT NULL, - "kycLevel" INTEGER NOT NULL DEFAULT 4, - "overallScore" INTEGER NOT NULL DEFAULT 0, - "privacyScore" INTEGER NOT NULL DEFAULT 0, - "trustScore" INTEGER NOT NULL DEFAULT 0, - "isRecentlyListed" BOOLEAN NOT NULL DEFAULT false, - "averageUserRating" DOUBLE PRECISION, - "serviceVisibility" "ServiceVisibility" NOT NULL DEFAULT 'PUBLIC', - "serviceInfoBanner" "ServiceInfoBanner" NOT NULL DEFAULT 'NONE', - "serviceInfoBannerNotes" TEXT, - "verificationStatus" "VerificationStatus" NOT NULL DEFAULT 'COMMUNITY_CONTRIBUTED', - "verificationSummary" TEXT, - "verificationProofMd" TEXT, - "verifiedAt" TIMESTAMP(3), - "userSentiment" JSONB, - "userSentimentAt" TIMESTAMP(3), - "referral" TEXT, - "acceptedCurrencies" "Currency"[] DEFAULT ARRAY[]::"Currency"[], - "serviceUrls" TEXT[], - "tosUrls" TEXT[] DEFAULT ARRAY[]::TEXT[], - "onionUrls" TEXT[] DEFAULT ARRAY[]::TEXT[], - "i2pUrls" TEXT[] DEFAULT ARRAY[]::TEXT[], - "imageUrl" TEXT, - "tosReview" JSONB, - "tosReviewAt" TIMESTAMP(3), - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "listedAt" TIMESTAMP(3), - - CONSTRAINT "Service_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "ServiceContactMethod" ( - "id" SERIAL NOT NULL, - "label" TEXT NOT NULL, - "value" TEXT NOT NULL, - "iconId" TEXT NOT NULL, - "info" TEXT NOT NULL, - "serviceId" INTEGER NOT NULL, - - CONSTRAINT "ServiceContactMethod_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Attribute" ( - "id" SERIAL NOT NULL, - "slug" TEXT NOT NULL, - "title" TEXT NOT NULL, - "description" TEXT NOT NULL, - "privacyPoints" INTEGER NOT NULL DEFAULT 0, - "trustPoints" INTEGER NOT NULL DEFAULT 0, - "category" "AttributeCategory" NOT NULL, - "type" "AttributeType" NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "Attribute_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "InternalUserNote" ( - "id" SERIAL NOT NULL, - "content" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "userId" INTEGER NOT NULL, - "addedByUserId" INTEGER, - - CONSTRAINT "InternalUserNote_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "User" ( - "id" SERIAL NOT NULL, - "name" TEXT NOT NULL, - "displayName" TEXT, - "link" TEXT, - "picture" TEXT, - "spammer" BOOLEAN NOT NULL DEFAULT false, - "verified" BOOLEAN NOT NULL DEFAULT false, - "admin" BOOLEAN NOT NULL DEFAULT false, - "verifier" BOOLEAN NOT NULL DEFAULT false, - "verifiedLink" TEXT, - "secretTokenHash" TEXT NOT NULL, - "totalKarma" INTEGER NOT NULL DEFAULT 0, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "User_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "CommentVote" ( - "id" SERIAL NOT NULL, - "downvote" BOOLEAN NOT NULL DEFAULT false, - "commentId" INTEGER NOT NULL, - "userId" INTEGER NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "CommentVote_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "ServiceAttribute" ( - "serviceId" INTEGER NOT NULL, - "attributeId" INTEGER NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "ServiceAttribute_pkey" PRIMARY KEY ("serviceId","attributeId") -); - --- CreateTable -CREATE TABLE "KarmaTransaction" ( - "id" SERIAL NOT NULL, - "userId" INTEGER NOT NULL, - "action" TEXT NOT NULL, - "points" INTEGER NOT NULL DEFAULT 0, - "commentId" INTEGER, - "suggestionId" INTEGER, - "description" TEXT NOT NULL, - "processed" BOOLEAN NOT NULL DEFAULT false, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "KarmaTransaction_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "VerificationStep" ( - "id" SERIAL NOT NULL, - "title" TEXT NOT NULL, - "description" TEXT NOT NULL, - "status" "VerificationStepStatus" NOT NULL DEFAULT 'PENDING', - "evidenceMd" TEXT, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "serviceId" INTEGER NOT NULL, - - CONSTRAINT "VerificationStep_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Category" ( - "id" SERIAL NOT NULL, - "name" TEXT NOT NULL, - "icon" TEXT NOT NULL, - "slug" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "Category_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "ServiceVerificationRequest" ( - "id" SERIAL NOT NULL, - "serviceId" INTEGER NOT NULL, - "userId" INTEGER NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "ServiceVerificationRequest_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "ServiceScoreRecalculationJob" ( - "id" SERIAL NOT NULL, - "serviceId" INTEGER NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "processedAt" TIMESTAMP(3), - - CONSTRAINT "ServiceScoreRecalculationJob_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "ServiceUser" ( - "id" SERIAL NOT NULL, - "userId" INTEGER NOT NULL, - "serviceId" INTEGER NOT NULL, - "role" "ServiceUserRole" NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "ServiceUser_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "_watchedComments" ( - "A" INTEGER NOT NULL, - "B" INTEGER NOT NULL, - - CONSTRAINT "_watchedComments_AB_pkey" PRIMARY KEY ("A","B") -); - --- CreateTable -CREATE TABLE "_onEventCreatedForServices" ( - "A" INTEGER NOT NULL, - "B" INTEGER NOT NULL, - - CONSTRAINT "_onEventCreatedForServices_AB_pkey" PRIMARY KEY ("A","B") -); - --- CreateTable -CREATE TABLE "_onRootCommentCreatedForServices" ( - "A" INTEGER NOT NULL, - "B" INTEGER NOT NULL, - - CONSTRAINT "_onRootCommentCreatedForServices_AB_pkey" PRIMARY KEY ("A","B") -); - --- CreateTable -CREATE TABLE "_onVerificationChangeForServices" ( - "A" INTEGER NOT NULL, - "B" INTEGER NOT NULL, - - CONSTRAINT "_onVerificationChangeForServices_AB_pkey" PRIMARY KEY ("A","B") -); - --- CreateTable -CREATE TABLE "_AttributeToNotificationPreferenceOnServiceVerificationChangeFi" ( - "A" INTEGER NOT NULL, - "B" INTEGER NOT NULL, - - CONSTRAINT "_AttributeToNotificationPreferenceOnServiceVerification_AB_pkey" PRIMARY KEY ("A","B") -); - --- CreateTable -CREATE TABLE "_ServiceToCategory" ( - "A" INTEGER NOT NULL, - "B" INTEGER NOT NULL, - - CONSTRAINT "_ServiceToCategory_AB_pkey" PRIMARY KEY ("A","B") -); - --- CreateTable -CREATE TABLE "_CategoryToNotificationPreferenceOnServiceVerificationChangeFil" ( - "A" INTEGER NOT NULL, - "B" INTEGER NOT NULL, - - CONSTRAINT "_CategoryToNotificationPreferenceOnServiceVerificationC_AB_pkey" PRIMARY KEY ("A","B") -); - --- CreateIndex -CREATE INDEX "Comment_status_idx" ON "Comment"("status"); - --- CreateIndex -CREATE INDEX "Comment_createdAt_idx" ON "Comment"("createdAt"); - --- CreateIndex -CREATE INDEX "Comment_serviceId_idx" ON "Comment"("serviceId"); - --- CreateIndex -CREATE INDEX "Comment_authorId_idx" ON "Comment"("authorId"); - --- CreateIndex -CREATE INDEX "Comment_upvotes_idx" ON "Comment"("upvotes"); - --- CreateIndex -CREATE INDEX "Comment_rating_idx" ON "Comment"("rating"); - --- CreateIndex -CREATE INDEX "Comment_ratingActive_idx" ON "Comment"("ratingActive"); - --- CreateIndex -CREATE UNIQUE INDEX "Comment_serviceId_orderId_key" ON "Comment"("serviceId", "orderId"); - --- CreateIndex -CREATE INDEX "Notification_userId_idx" ON "Notification"("userId"); - --- CreateIndex -CREATE INDEX "Notification_read_idx" ON "Notification"("read"); - --- CreateIndex -CREATE INDEX "Notification_createdAt_idx" ON "Notification"("createdAt"); - --- CreateIndex -CREATE INDEX "Notification_userId_read_createdAt_idx" ON "Notification"("userId", "read", "createdAt"); - --- CreateIndex -CREATE INDEX "Notification_userId_type_aboutCommentId_idx" ON "Notification"("userId", "type", "aboutCommentId"); - --- CreateIndex -CREATE INDEX "idx_notification_suggestion_message" ON "Notification"("userId", "type", "aboutServiceSuggestionMessageId"); - --- CreateIndex -CREATE INDEX "idx_notification_suggestion_status" ON "Notification"("userId", "type", "aboutServiceSuggestionId"); - --- CreateIndex -CREATE INDEX "idx_notification_account_status" ON "Notification"("userId", "type", "aboutAccountStatusChange"); - --- CreateIndex -CREATE UNIQUE INDEX "NotificationPreferences_userId_key" ON "NotificationPreferences"("userId"); - --- CreateIndex -CREATE UNIQUE INDEX "NotificationPreferenceOnServiceVerificationChangeFilterFilt_key" ON "NotificationPreferenceOnServiceVerificationChangeFilterFilter"("verificationStatus", "notificationPreferencesId"); - --- CreateIndex -CREATE INDEX "Event_visible_idx" ON "Event"("visible"); - --- CreateIndex -CREATE INDEX "Event_startedAt_idx" ON "Event"("startedAt"); - --- CreateIndex -CREATE INDEX "Event_createdAt_idx" ON "Event"("createdAt"); - --- CreateIndex -CREATE INDEX "Event_endedAt_idx" ON "Event"("endedAt"); - --- CreateIndex -CREATE INDEX "Event_type_idx" ON "Event"("type"); - --- CreateIndex -CREATE INDEX "Event_serviceId_idx" ON "Event"("serviceId"); - --- CreateIndex -CREATE INDEX "ServiceSuggestion_userId_idx" ON "ServiceSuggestion"("userId"); - --- CreateIndex -CREATE INDEX "ServiceSuggestion_serviceId_idx" ON "ServiceSuggestion"("serviceId"); - --- CreateIndex -CREATE INDEX "ServiceSuggestionMessage_userId_idx" ON "ServiceSuggestionMessage"("userId"); - --- CreateIndex -CREATE INDEX "ServiceSuggestionMessage_suggestionId_idx" ON "ServiceSuggestionMessage"("suggestionId"); - --- CreateIndex -CREATE INDEX "ServiceSuggestionMessage_createdAt_idx" ON "ServiceSuggestionMessage"("createdAt"); - --- CreateIndex -CREATE UNIQUE INDEX "Service_slug_key" ON "Service"("slug"); - --- CreateIndex -CREATE INDEX "Service_listedAt_idx" ON "Service"("listedAt"); - --- CreateIndex -CREATE INDEX "Service_overallScore_idx" ON "Service"("overallScore"); - --- CreateIndex -CREATE INDEX "Service_privacyScore_idx" ON "Service"("privacyScore"); - --- CreateIndex -CREATE INDEX "Service_trustScore_idx" ON "Service"("trustScore"); - --- CreateIndex -CREATE INDEX "Service_averageUserRating_idx" ON "Service"("averageUserRating"); - --- CreateIndex -CREATE INDEX "Service_name_idx" ON "Service"("name"); - --- CreateIndex -CREATE INDEX "Service_verificationStatus_idx" ON "Service"("verificationStatus"); - --- CreateIndex -CREATE INDEX "Service_kycLevel_idx" ON "Service"("kycLevel"); - --- CreateIndex -CREATE INDEX "Service_createdAt_idx" ON "Service"("createdAt"); - --- CreateIndex -CREATE INDEX "Service_updatedAt_idx" ON "Service"("updatedAt"); - --- CreateIndex -CREATE INDEX "Service_slug_idx" ON "Service"("slug"); - --- CreateIndex -CREATE UNIQUE INDEX "Attribute_slug_key" ON "Attribute"("slug"); - --- CreateIndex -CREATE INDEX "InternalUserNote_userId_idx" ON "InternalUserNote"("userId"); - --- CreateIndex -CREATE INDEX "InternalUserNote_addedByUserId_idx" ON "InternalUserNote"("addedByUserId"); - --- CreateIndex -CREATE INDEX "InternalUserNote_createdAt_idx" ON "InternalUserNote"("createdAt"); - --- CreateIndex -CREATE UNIQUE INDEX "User_name_key" ON "User"("name"); - --- CreateIndex -CREATE UNIQUE INDEX "User_secretTokenHash_key" ON "User"("secretTokenHash"); - --- CreateIndex -CREATE INDEX "User_createdAt_idx" ON "User"("createdAt"); - --- CreateIndex -CREATE INDEX "User_totalKarma_idx" ON "User"("totalKarma"); - --- CreateIndex -CREATE INDEX "CommentVote_commentId_idx" ON "CommentVote"("commentId"); - --- CreateIndex -CREATE INDEX "CommentVote_userId_idx" ON "CommentVote"("userId"); - --- CreateIndex -CREATE UNIQUE INDEX "CommentVote_commentId_userId_key" ON "CommentVote"("commentId", "userId"); - --- CreateIndex -CREATE INDEX "KarmaTransaction_createdAt_idx" ON "KarmaTransaction"("createdAt"); - --- CreateIndex -CREATE INDEX "KarmaTransaction_userId_idx" ON "KarmaTransaction"("userId"); - --- CreateIndex -CREATE INDEX "KarmaTransaction_processed_idx" ON "KarmaTransaction"("processed"); - --- CreateIndex -CREATE INDEX "KarmaTransaction_suggestionId_idx" ON "KarmaTransaction"("suggestionId"); - --- CreateIndex -CREATE INDEX "KarmaTransaction_commentId_idx" ON "KarmaTransaction"("commentId"); - --- CreateIndex -CREATE INDEX "VerificationStep_serviceId_idx" ON "VerificationStep"("serviceId"); - --- CreateIndex -CREATE INDEX "VerificationStep_status_idx" ON "VerificationStep"("status"); - --- CreateIndex -CREATE INDEX "VerificationStep_createdAt_idx" ON "VerificationStep"("createdAt"); - --- CreateIndex -CREATE UNIQUE INDEX "Category_name_key" ON "Category"("name"); - --- CreateIndex -CREATE UNIQUE INDEX "Category_slug_key" ON "Category"("slug"); - --- CreateIndex -CREATE INDEX "Category_name_idx" ON "Category"("name"); - --- CreateIndex -CREATE INDEX "Category_slug_idx" ON "Category"("slug"); - --- CreateIndex -CREATE INDEX "ServiceVerificationRequest_serviceId_idx" ON "ServiceVerificationRequest"("serviceId"); - --- CreateIndex -CREATE INDEX "ServiceVerificationRequest_userId_idx" ON "ServiceVerificationRequest"("userId"); - --- CreateIndex -CREATE INDEX "ServiceVerificationRequest_createdAt_idx" ON "ServiceVerificationRequest"("createdAt"); - --- CreateIndex -CREATE UNIQUE INDEX "ServiceVerificationRequest_serviceId_userId_key" ON "ServiceVerificationRequest"("serviceId", "userId"); - --- CreateIndex -CREATE UNIQUE INDEX "ServiceScoreRecalculationJob_serviceId_key" ON "ServiceScoreRecalculationJob"("serviceId"); - --- CreateIndex -CREATE INDEX "ServiceScoreRecalculationJob_processedAt_idx" ON "ServiceScoreRecalculationJob"("processedAt"); - --- CreateIndex -CREATE INDEX "ServiceScoreRecalculationJob_createdAt_idx" ON "ServiceScoreRecalculationJob"("createdAt"); - --- CreateIndex -CREATE INDEX "ServiceUser_userId_idx" ON "ServiceUser"("userId"); - --- CreateIndex -CREATE INDEX "ServiceUser_serviceId_idx" ON "ServiceUser"("serviceId"); - --- CreateIndex -CREATE INDEX "ServiceUser_role_idx" ON "ServiceUser"("role"); - --- CreateIndex -CREATE UNIQUE INDEX "ServiceUser_userId_serviceId_key" ON "ServiceUser"("userId", "serviceId"); - --- CreateIndex -CREATE INDEX "_watchedComments_B_index" ON "_watchedComments"("B"); - --- CreateIndex -CREATE INDEX "_onEventCreatedForServices_B_index" ON "_onEventCreatedForServices"("B"); - --- CreateIndex -CREATE INDEX "_onRootCommentCreatedForServices_B_index" ON "_onRootCommentCreatedForServices"("B"); - --- CreateIndex -CREATE INDEX "_onVerificationChangeForServices_B_index" ON "_onVerificationChangeForServices"("B"); - --- CreateIndex -CREATE INDEX "_AttributeToNotificationPreferenceOnServiceVerification_B_index" ON "_AttributeToNotificationPreferenceOnServiceVerificationChangeFi"("B"); - --- CreateIndex -CREATE INDEX "_ServiceToCategory_B_index" ON "_ServiceToCategory"("B"); - --- CreateIndex -CREATE INDEX "_CategoryToNotificationPreferenceOnServiceVerificationC_B_index" ON "_CategoryToNotificationPreferenceOnServiceVerificationChangeFil"("B"); - --- AddForeignKey -ALTER TABLE "Comment" ADD CONSTRAINT "Comment_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Comment" ADD CONSTRAINT "Comment_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Comment" ADD CONSTRAINT "Comment_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "Comment"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Notification" ADD CONSTRAINT "Notification_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Notification" ADD CONSTRAINT "Notification_aboutCommentId_fkey" FOREIGN KEY ("aboutCommentId") REFERENCES "Comment"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Notification" ADD CONSTRAINT "Notification_aboutEventId_fkey" FOREIGN KEY ("aboutEventId") REFERENCES "Event"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Notification" ADD CONSTRAINT "Notification_aboutServiceId_fkey" FOREIGN KEY ("aboutServiceId") REFERENCES "Service"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Notification" ADD CONSTRAINT "Notification_aboutServiceSuggestionId_fkey" FOREIGN KEY ("aboutServiceSuggestionId") REFERENCES "ServiceSuggestion"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Notification" ADD CONSTRAINT "Notification_aboutServiceSuggestionMessageId_fkey" FOREIGN KEY ("aboutServiceSuggestionMessageId") REFERENCES "ServiceSuggestionMessage"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "NotificationPreferences" ADD CONSTRAINT "NotificationPreferences_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "NotificationPreferenceOnServiceVerificationChangeFilterFilter" ADD CONSTRAINT "NotificationPreferenceOnServiceVerificationChangeFilterFil_fkey" FOREIGN KEY ("notificationPreferencesId") REFERENCES "NotificationPreferences"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Event" ADD CONSTRAINT "Event_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "ServiceSuggestion" ADD CONSTRAINT "ServiceSuggestion_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "ServiceSuggestion" ADD CONSTRAINT "ServiceSuggestion_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "ServiceSuggestionMessage" ADD CONSTRAINT "ServiceSuggestionMessage_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "ServiceSuggestionMessage" ADD CONSTRAINT "ServiceSuggestionMessage_suggestionId_fkey" FOREIGN KEY ("suggestionId") REFERENCES "ServiceSuggestion"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "ServiceContactMethod" ADD CONSTRAINT "ServiceContactMethod_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "InternalUserNote" ADD CONSTRAINT "InternalUserNote_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "InternalUserNote" ADD CONSTRAINT "InternalUserNote_addedByUserId_fkey" FOREIGN KEY ("addedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "CommentVote" ADD CONSTRAINT "CommentVote_commentId_fkey" FOREIGN KEY ("commentId") REFERENCES "Comment"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "CommentVote" ADD CONSTRAINT "CommentVote_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "ServiceAttribute" ADD CONSTRAINT "ServiceAttribute_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "ServiceAttribute" ADD CONSTRAINT "ServiceAttribute_attributeId_fkey" FOREIGN KEY ("attributeId") REFERENCES "Attribute"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "KarmaTransaction" ADD CONSTRAINT "KarmaTransaction_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "KarmaTransaction" ADD CONSTRAINT "KarmaTransaction_commentId_fkey" FOREIGN KEY ("commentId") REFERENCES "Comment"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "KarmaTransaction" ADD CONSTRAINT "KarmaTransaction_suggestionId_fkey" FOREIGN KEY ("suggestionId") REFERENCES "ServiceSuggestion"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "VerificationStep" ADD CONSTRAINT "VerificationStep_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "ServiceVerificationRequest" ADD CONSTRAINT "ServiceVerificationRequest_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "ServiceVerificationRequest" ADD CONSTRAINT "ServiceVerificationRequest_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "ServiceUser" ADD CONSTRAINT "ServiceUser_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "ServiceUser" ADD CONSTRAINT "ServiceUser_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_watchedComments" ADD CONSTRAINT "_watchedComments_A_fkey" FOREIGN KEY ("A") REFERENCES "Comment"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_watchedComments" ADD CONSTRAINT "_watchedComments_B_fkey" FOREIGN KEY ("B") REFERENCES "NotificationPreferences"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_onEventCreatedForServices" ADD CONSTRAINT "_onEventCreatedForServices_A_fkey" FOREIGN KEY ("A") REFERENCES "NotificationPreferences"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_onEventCreatedForServices" ADD CONSTRAINT "_onEventCreatedForServices_B_fkey" FOREIGN KEY ("B") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_onRootCommentCreatedForServices" ADD CONSTRAINT "_onRootCommentCreatedForServices_A_fkey" FOREIGN KEY ("A") REFERENCES "NotificationPreferences"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_onRootCommentCreatedForServices" ADD CONSTRAINT "_onRootCommentCreatedForServices_B_fkey" FOREIGN KEY ("B") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_onVerificationChangeForServices" ADD CONSTRAINT "_onVerificationChangeForServices_A_fkey" FOREIGN KEY ("A") REFERENCES "NotificationPreferences"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_onVerificationChangeForServices" ADD CONSTRAINT "_onVerificationChangeForServices_B_fkey" FOREIGN KEY ("B") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_AttributeToNotificationPreferenceOnServiceVerificationChangeFi" ADD CONSTRAINT "_AttributeToNotificationPreferenceOnServiceVerificationC_A_fkey" FOREIGN KEY ("A") REFERENCES "Attribute"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_AttributeToNotificationPreferenceOnServiceVerificationChangeFi" ADD CONSTRAINT "_AttributeToNotificationPreferenceOnServiceVerificationC_B_fkey" FOREIGN KEY ("B") REFERENCES "NotificationPreferenceOnServiceVerificationChangeFilterFilter"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_ServiceToCategory" ADD CONSTRAINT "_ServiceToCategory_A_fkey" FOREIGN KEY ("A") REFERENCES "Category"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_ServiceToCategory" ADD CONSTRAINT "_ServiceToCategory_B_fkey" FOREIGN KEY ("B") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_CategoryToNotificationPreferenceOnServiceVerificationChangeFil" ADD CONSTRAINT "_CategoryToNotificationPreferenceOnServiceVerificationCh_A_fkey" FOREIGN KEY ("A") REFERENCES "Category"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_CategoryToNotificationPreferenceOnServiceVerificationChangeFil" ADD CONSTRAINT "_CategoryToNotificationPreferenceOnServiceVerificationCh_B_fkey" FOREIGN KEY ("B") REFERENCES "NotificationPreferenceOnServiceVerificationChangeFilterFilter"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/web/prisma/migrations/20250519091940_last_login_at/migration.sql b/web/prisma/migrations/20250519091940_last_login_at/migration.sql deleted file mode 100644 index 21ace98..0000000 --- a/web/prisma/migrations/20250519091940_last_login_at/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "User" ADD COLUMN "lastLoginAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; diff --git a/web/prisma/migrations/migration_lock.toml b/web/prisma/migrations/migration_lock.toml deleted file mode 100644 index 044d57c..0000000 --- a/web/prisma/migrations/migration_lock.toml +++ /dev/null @@ -1,3 +0,0 @@ -# Please do not edit this file manually -# It should be added in your version-control system (e.g., Git) -provider = "postgresql" diff --git a/web/prisma/schema.prisma b/web/prisma/schema.prisma deleted file mode 100644 index 946a589..0000000 --- a/web/prisma/schema.prisma +++ /dev/null @@ -1,590 +0,0 @@ -// This is your Prisma schema file - -datasource db { - provider = "postgres" - url = env("DATABASE_URL") -} - -generator client { - provider = "prisma-client-js" -} - -generator json { - provider = "prisma-json-types-generator" -} - -enum CommentStatus { - PENDING - HUMAN_PENDING - APPROVED - VERIFIED - REJECTED -} - -enum OrderIdStatus { - PENDING - APPROVED - REJECTED -} - -model Comment { - id Int @id @default(autoincrement()) - /// Computed via trigger. Do not update through prisma. - upvotes Int @default(0) - status CommentStatus @default(PENDING) - suspicious Boolean @default(false) - requiresAdminReview Boolean @default(false) - communityNote String? - verificationNote String? - internalNote String? - privateContext String? - orderId String? @db.VarChar(100) - orderIdStatus OrderIdStatus? @default(PENDING) - kycRequested Boolean @default(false) - fundsBlocked Boolean @default(false) - content String - rating Int? @db.SmallInt - ratingActive Boolean @default(false) - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt - author User @relation(fields: [authorId], references: [id], onDelete: Cascade) - authorId Int - service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade) - serviceId Int - parentId Int? - parent Comment? @relation("CommentReplies", fields: [parentId], references: [id], onDelete: Cascade) - replies Comment[] @relation("CommentReplies") - karmaTransactions KarmaTransaction[] - votes CommentVote[] - - notificationPreferenceswatchedComments NotificationPreferences[] @relation("watchedComments") - Notification Notification[] - - @@unique([serviceId, orderId], name: "unique_orderId_per_service") - @@index([status]) - @@index([createdAt]) - @@index([serviceId]) - @@index([authorId]) - @@index([upvotes]) - @@index([rating]) - @@index([ratingActive]) -} - -enum VerificationStatus { - COMMUNITY_CONTRIBUTED - // COMMUNITY_VERIFIED - APPROVED - VERIFICATION_SUCCESS - VERIFICATION_FAILED -} - -enum ServiceInfoBanner { - NONE - NO_LONGER_OPERATIONAL -} - -enum ServiceVisibility { - PUBLIC - UNLISTED - HIDDEN -} - -enum Currency { - MONERO - BITCOIN - LIGHTNING - FIAT - CASH -} - -enum EventType { - WARNING - WARNING_SOLVED - ALERT - ALERT_SOLVED - INFO - NORMAL - UPDATE -} - -enum ServiceUserRole { - OWNER - ADMIN - MODERATOR - SUPPORT - TEAM_MEMBER -} - -enum AccountStatusChange { - ADMIN_TRUE - ADMIN_FALSE - VERIFIED_TRUE - VERIFIED_FALSE - VERIFIER_TRUE - VERIFIER_FALSE - SPAMMER_TRUE - SPAMMER_FALSE -} - -enum NotificationType { - COMMENT_STATUS_CHANGE - REPLY_COMMENT_CREATED - COMMUNITY_NOTE_ADDED - /// Comment that is not a reply. May include a rating. - ROOT_COMMENT_CREATED - SUGGESTION_MESSAGE - SUGGESTION_STATUS_CHANGE - // KARMA_UNLOCK // TODO: [KARMA_UNLOCK] Will be added later, when karma unloks are in the database, not in the code. - /// Marked as spammer, promoted to admin, etc. - ACCOUNT_STATUS_CHANGE - EVENT_CREATED - SERVICE_VERIFICATION_STATUS_CHANGE -} - -enum CommentStatusChange { - MARKED_AS_SPAM - UNMARKED_AS_SPAM - MARKED_FOR_ADMIN_REVIEW - UNMARKED_FOR_ADMIN_REVIEW - STATUS_CHANGED_TO_APPROVED - STATUS_CHANGED_TO_VERIFIED - STATUS_CHANGED_TO_REJECTED - STATUS_CHANGED_TO_PENDING -} - -enum ServiceVerificationStatusChange { - STATUS_CHANGED_TO_COMMUNITY_CONTRIBUTED - STATUS_CHANGED_TO_APPROVED - STATUS_CHANGED_TO_VERIFICATION_SUCCESS - STATUS_CHANGED_TO_VERIFICATION_FAILED -} - -enum ServiceSuggestionStatusChange { - STATUS_CHANGED_TO_PENDING - STATUS_CHANGED_TO_APPROVED - STATUS_CHANGED_TO_REJECTED - STATUS_CHANGED_TO_WITHDRAWN -} - -model Notification { - id Int @id @default(autoincrement()) - userId Int - user User @relation("NotificationOwner", fields: [userId], references: [id], onDelete: Cascade) - type NotificationType - read Boolean @default(false) - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt - - aboutComment Comment? @relation(fields: [aboutCommentId], references: [id]) - aboutCommentId Int? - aboutEvent Event? @relation(fields: [aboutEventId], references: [id]) - aboutEventId Int? - aboutService Service? @relation(fields: [aboutServiceId], references: [id]) - aboutServiceId Int? - aboutServiceSuggestion ServiceSuggestion? @relation(fields: [aboutServiceSuggestionId], references: [id]) - aboutServiceSuggestionId Int? - aboutServiceSuggestionMessage ServiceSuggestionMessage? @relation(fields: [aboutServiceSuggestionMessageId], references: [id]) - aboutServiceSuggestionMessageId Int? - aboutAccountStatusChange AccountStatusChange? - aboutCommentStatusChange CommentStatusChange? - aboutServiceVerificationStatusChange ServiceVerificationStatusChange? - aboutSuggestionStatusChange ServiceSuggestionStatusChange? - - @@index([userId]) - @@index([read]) - @@index([createdAt]) - @@index([userId, read, createdAt]) - @@index([userId, type, aboutCommentId]) - @@index([userId, type, aboutServiceSuggestionMessageId], map: "idx_notification_suggestion_message") - @@index([userId, type, aboutServiceSuggestionId], map: "idx_notification_suggestion_status") - @@index([userId, type, aboutAccountStatusChange], map: "idx_notification_account_status") -} - -model NotificationPreferences { - id Int @id @default(autoincrement()) - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt - - userId Int @unique - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - - enableOnMyCommentStatusChange Boolean @default(true) - enableAutowatchMyComments Boolean @default(true) - enableNotifyPendingRepliesOnWatch Boolean @default(false) - - onEventCreatedForServices Service[] @relation("onEventCreatedForServices") - onRootCommentCreatedForServices Service[] @relation("onRootCommentCreatedForServices") - onVerificationChangeForServices Service[] @relation("onVerificationChangeForServices") - watchedComments Comment[] @relation("watchedComments") - - onServiceVerificationChangeFilter NotificationPreferenceOnServiceVerificationChangeFilterFilter[] -} - -model NotificationPreferenceOnServiceVerificationChangeFilterFilter { - id Int @id @default(autoincrement()) - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt - - verificationStatus VerificationStepStatus - notificationPreferences NotificationPreferences @relation(fields: [notificationPreferencesId], references: [id], onDelete: Cascade) - notificationPreferencesId Int - - categories Category[] - attributes Attribute[] - currencies Currency[] - /// 0-10 - scores Int[] - - @@unique([verificationStatus, notificationPreferencesId]) -} - -model Event { - id Int @id @default(autoincrement()) - title String - content String - source String? - type EventType - visible Boolean @default(true) - startedAt DateTime - /// If null, the event is ongoing. If same as startedAt, the event is a one-time event. If startedAt is in the future, the event is upcoming. - endedAt DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt - service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade) - serviceId Int - Notification Notification[] - - @@index([visible]) - @@index([startedAt]) - @@index([createdAt]) - @@index([endedAt]) - @@index([type]) - @@index([serviceId]) -} - -enum ServiceSuggestionStatus { - PENDING - APPROVED - REJECTED - WITHDRAWN -} - -enum ServiceSuggestionType { - CREATE_SERVICE - EDIT_SERVICE -} - -model ServiceSuggestion { - id Int @id @default(autoincrement()) - type ServiceSuggestionType - status ServiceSuggestionStatus @default(PENDING) - notes String? - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt - userId Int - serviceId Int - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade) - messages ServiceSuggestionMessage[] - Notification Notification[] - KarmaTransaction KarmaTransaction[] - - @@index([userId]) - @@index([serviceId]) -} - -model ServiceSuggestionMessage { - id Int @id @default(autoincrement()) - content String - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt - userId Int - suggestionId Int - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - suggestion ServiceSuggestion @relation(fields: [suggestionId], references: [id], onDelete: Cascade) - notifications Notification[] - - @@index([userId]) - @@index([suggestionId]) - @@index([createdAt]) -} - -model Service { - id Int @id @default(autoincrement()) - name String - slug String @unique - description String - categories Category[] @relation("ServiceToCategory") - kycLevel Int @default(4) - overallScore Int @default(0) - privacyScore Int @default(0) - trustScore Int @default(0) - /// Computed via trigger. Do not update through prisma. - isRecentlyListed Boolean @default(false) - /// Computed via trigger. Do not update through prisma. - averageUserRating Float? - serviceVisibility ServiceVisibility @default(PUBLIC) - serviceInfoBanner ServiceInfoBanner @default(NONE) - serviceInfoBannerNotes String? - verificationStatus VerificationStatus @default(COMMUNITY_CONTRIBUTED) - verificationSummary String? - verificationRequests ServiceVerificationRequest[] - verificationProofMd String? - /// Computed via trigger when the service status is VERIFICATION_SUCCESS. Do not update through prisma. - verifiedAt DateTime? - /// [UserSentiment] - userSentiment Json? - userSentimentAt DateTime? - referral String? - acceptedCurrencies Currency[] @default([]) - serviceUrls String[] - tosUrls String[] @default([]) - onionUrls String[] @default([]) - i2pUrls String[] @default([]) - imageUrl String? - /// [TosReview] - tosReview Json? - tosReviewAt DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt - listedAt DateTime? - comments Comment[] - events Event[] - contactMethods ServiceContactMethod[] @relation("ServiceToContactMethod") - attributes ServiceAttribute[] - verificationSteps VerificationStep[] - suggestions ServiceSuggestion[] - - onEventCreatedForServices NotificationPreferences[] @relation("onEventCreatedForServices") - onRootCommentCreatedForServices NotificationPreferences[] @relation("onRootCommentCreatedForServices") - onVerificationChangeForServices NotificationPreferences[] @relation("onVerificationChangeForServices") - Notification Notification[] - affiliatedUsers ServiceUser[] @relation("ServiceUsers") - - @@index([listedAt]) - @@index([overallScore]) - @@index([privacyScore]) - @@index([trustScore]) - @@index([averageUserRating]) - @@index([name]) - @@index([verificationStatus]) - @@index([kycLevel]) - @@index([createdAt]) - @@index([updatedAt]) - @@index([slug]) -} - -model ServiceContactMethod { - id Int @id @default(autoincrement()) - label String - /// Including the protocol (e.g. "mailto:", "tel:", "https://") - value String - iconId String - info String - services Service @relation("ServiceToContactMethod", fields: [serviceId], references: [id], onDelete: Cascade) - serviceId Int -} - -enum AttributeCategory { - PRIVACY - TRUST -} - -enum AttributeType { - GOOD - BAD - WARNING - INFO -} - -model Attribute { - id Int @id @default(autoincrement()) - slug String @unique - title String - /// Markdown - description String - privacyPoints Int @default(0) - trustPoints Int @default(0) - category AttributeCategory - type AttributeType - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt - services ServiceAttribute[] - - notificationPreferencesOnServiceVerificationChange NotificationPreferenceOnServiceVerificationChangeFilterFilter[] -} - -model InternalUserNote { - id Int @id @default(autoincrement()) - content String - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt - - user User @relation("UserRecievedNotes", fields: [userId], references: [id], onDelete: Cascade) - userId Int - addedByUser User? @relation("UserAddedNotes", fields: [addedByUserId], references: [id], onDelete: SetNull) - addedByUserId Int? - - @@index([userId]) - @@index([addedByUserId]) - @@index([createdAt]) -} - -model User { - id Int @id @default(autoincrement()) - name String @unique - displayName String? - link String? - picture String? - spammer Boolean @default(false) - verified Boolean @default(false) - admin Boolean @default(false) - verifier Boolean @default(false) - verifiedLink String? - secretTokenHash String @unique - /// Computed via trigger. Do not update through prisma. - totalKarma Int @default(0) - - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt - lastLoginAt DateTime @default(now()) - comments Comment[] - karmaTransactions KarmaTransaction[] - commentVotes CommentVote[] - suggestions ServiceSuggestion[] - suggestionMessages ServiceSuggestionMessage[] - internalNotes InternalUserNote[] @relation("UserRecievedNotes") - addedInternalNotes InternalUserNote[] @relation("UserAddedNotes") - verificationRequests ServiceVerificationRequest[] - notifications Notification[] @relation("NotificationOwner") - notificationPreferences NotificationPreferences? - serviceAffiliations ServiceUser[] @relation("UserServices") - - @@index([createdAt]) - @@index([totalKarma]) -} - -model CommentVote { - id Int @id @default(autoincrement()) - downvote Boolean @default(false) // false = upvote, true = downvote - comment Comment @relation(fields: [commentId], references: [id], onDelete: Cascade) - commentId Int - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - userId Int - createdAt DateTime @default(now()) - - @@unique([commentId, userId]) // Ensure one vote per user per comment - @@index([commentId]) - @@index([userId]) -} - -model ServiceAttribute { - service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade) - serviceId Int - attribute Attribute @relation(fields: [attributeId], references: [id], onDelete: Cascade) - attributeId Int - createdAt DateTime @default(now()) - - @@id([serviceId, attributeId]) -} - -model KarmaTransaction { - id Int @id @default(autoincrement()) - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - userId Int - action String - points Int @default(0) - comment Comment? @relation(fields: [commentId], references: [id], onDelete: Cascade) - commentId Int? - suggestion ServiceSuggestion? @relation(fields: [suggestionId], references: [id], onDelete: Cascade) - suggestionId Int? - description String - processed Boolean @default(false) - createdAt DateTime @default(now()) - - @@index([createdAt]) - @@index([userId]) - @@index([processed]) - @@index([suggestionId]) - @@index([commentId]) -} - -enum VerificationStepStatus { - PENDING - IN_PROGRESS - PASSED - FAILED -} - -model VerificationStep { - id Int @id @default(autoincrement()) - title String - description String - status VerificationStepStatus @default(PENDING) - evidenceMd String? - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt - service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade) - serviceId Int - - @@index([serviceId]) - @@index([status]) - @@index([createdAt]) -} - -model Category { - id Int @id @default(autoincrement()) - name String @unique - icon String - slug String @unique - - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt - services Service[] @relation("ServiceToCategory") - - notificationPreferencesOnServiceVerificationChange NotificationPreferenceOnServiceVerificationChangeFilterFilter[] - - @@index([name]) - @@index([slug]) -} - -model ServiceVerificationRequest { - id Int @id @default(autoincrement()) - service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade) - serviceId Int - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - userId Int - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt - - @@unique([serviceId, userId]) - @@index([serviceId]) - @@index([userId]) - @@index([createdAt]) -} - -model ServiceScoreRecalculationJob { - id Int @id @default(autoincrement()) - serviceId Int @unique - createdAt DateTime @default(now()) - processedAt DateTime? @updatedAt - - @@index([processedAt]) - @@index([createdAt]) -} - -model ServiceUser { - id Int @id @default(autoincrement()) - userId Int - user User @relation("UserServices", fields: [userId], references: [id], onDelete: Cascade) - serviceId Int - service Service @relation("ServiceUsers", fields: [serviceId], references: [id], onDelete: Cascade) - role ServiceUserRole - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - @@unique([userId, serviceId]) - @@index([userId]) - @@index([serviceId]) - @@index([role]) -} diff --git a/web/prisma/triggers/01_karma_tx.sql b/web/prisma/triggers/01_karma_tx.sql deleted file mode 100644 index dcd30d9..0000000 --- a/web/prisma/triggers/01_karma_tx.sql +++ /dev/null @@ -1,265 +0,0 @@ --- This script manages user karma based on comment interactions. It handles karma points --- for comment approvals, verifications, spam status changes, and votes (upvotes/downvotes). --- Karma transactions are recorded, and user karma totals are updated accordingly. - --- Drop existing triggers first -DROP TRIGGER IF EXISTS comment_status_change_trigger ON "Comment"; -DROP TRIGGER IF EXISTS comment_suspicious_change_trigger ON "Comment"; -DROP TRIGGER IF EXISTS comment_upvote_change_trigger ON "Comment"; -DROP TRIGGER IF EXISTS comment_vote_change_trigger ON "CommentVote"; -DROP TRIGGER IF EXISTS suggestion_status_change_trigger ON "ServiceSuggestion"; - --- Drop existing functions -DROP FUNCTION IF EXISTS handle_comment_upvote_change(); -DROP FUNCTION IF EXISTS handle_comment_status_change(); -DROP FUNCTION IF EXISTS handle_comment_approval(); -DROP FUNCTION IF EXISTS handle_comment_verification(); -DROP FUNCTION IF EXISTS handle_comment_spam_status(); -DROP FUNCTION IF EXISTS handle_comment_vote_change(); -DROP FUNCTION IF EXISTS insert_karma_transaction(); -DROP FUNCTION IF EXISTS update_user_karma(); -DROP FUNCTION IF EXISTS handle_suggestion_status_change(); - --- Helper function to insert karma transaction -CREATE OR REPLACE FUNCTION insert_karma_transaction( - p_user_id INT, - p_points INT, - p_action TEXT, - p_comment_id INT, - p_description TEXT, - p_suggestion_id INT DEFAULT NULL -) RETURNS VOID AS $$ -BEGIN - INSERT INTO "KarmaTransaction" ( - "userId", "points", "action", "commentId", - "suggestionId", - "description", "processed", "createdAt" - ) - VALUES ( - p_user_id, p_points, p_action, p_comment_id, - p_suggestion_id, - p_description, true, NOW() - ); -END; -$$ LANGUAGE plpgsql; - --- Helper function to update user karma -CREATE OR REPLACE FUNCTION update_user_karma( - p_user_id INT, - p_karma_change INT -) RETURNS VOID AS $$ -BEGIN - UPDATE "User" - SET "totalKarma" = "totalKarma" + p_karma_change - WHERE id = p_user_id; -END; -$$ LANGUAGE plpgsql; - --- Handle comment approval -CREATE OR REPLACE FUNCTION handle_comment_approval( - NEW RECORD, - OLD RECORD -) RETURNS VOID AS $$ -BEGIN - IF OLD.status = 'PENDING' AND NEW.status = 'APPROVED' THEN - PERFORM insert_karma_transaction( - NEW."authorId", - 1, - 'comment_approved', - NEW.id, - format('Your comment #comment-%s in %s has been approved!', - NEW.id, - (SELECT name FROM "Service" WHERE id = NEW."serviceId")) - ); - PERFORM update_user_karma(NEW."authorId", 1); - END IF; -END; -$$ LANGUAGE plpgsql; - --- Handle comment verification -CREATE OR REPLACE FUNCTION handle_comment_verification( - NEW RECORD, - OLD RECORD -) RETURNS VOID AS $$ -BEGIN - IF NEW.status = 'VERIFIED' AND OLD.status != 'VERIFIED' THEN - PERFORM insert_karma_transaction( - NEW."authorId", - 5, - 'comment_verified', - NEW.id, - format('Your comment #comment-%s in %s has been verified!', - NEW.id, - (SELECT name FROM "Service" WHERE id = NEW."serviceId")) - ); - PERFORM update_user_karma(NEW."authorId", 5); - END IF; -END; -$$ LANGUAGE plpgsql; - --- Handle spam status changes -CREATE OR REPLACE FUNCTION handle_comment_spam_status( - NEW RECORD, - OLD RECORD -) RETURNS VOID AS $$ -BEGIN - -- Handle marking as spam - IF NEW.suspicious = true AND OLD.suspicious = false THEN - PERFORM insert_karma_transaction( - NEW."authorId", - -10, - 'comment_spam', - NEW.id, - format('Your comment #comment-%s in %s has been marked as spam.', - NEW.id, - (SELECT name FROM "Service" WHERE id = NEW."serviceId")) - ); - PERFORM update_user_karma(NEW."authorId", -10); - -- Handle unmarking as spam - ELSIF NEW.suspicious = false AND OLD.suspicious = true THEN - PERFORM insert_karma_transaction( - NEW."authorId", - 10, - 'comment_spam_reverted', - NEW.id, - format('Your comment #comment-%s in %s is no longer marked as spam.', - NEW.id, - (SELECT name FROM "Service" WHERE id = NEW."serviceId")) - ); - PERFORM update_user_karma(NEW."authorId", 10); - END IF; -END; -$$ LANGUAGE plpgsql; - --- Function for handling vote changes -CREATE OR REPLACE FUNCTION handle_comment_vote_change() -RETURNS TRIGGER AS $$ -DECLARE - karma_points INT; - vote_action TEXT; - vote_description TEXT; - comment_author_id INT; - service_name TEXT; - upvote_change INT := 0; -- Variable to track change in upvotes -BEGIN - -- Get comment author and service info - SELECT c."authorId", s.name INTO comment_author_id, service_name - FROM "Comment" c - JOIN "Service" s ON c.id = COALESCE(NEW."commentId", OLD."commentId") AND c."serviceId" = s.id; - - -- Calculate karma impact based on vote type - IF TG_OP = 'INSERT' THEN - -- New vote - karma_points := CASE WHEN NEW.downvote THEN -1 ELSE 1 END; - vote_action := CASE WHEN NEW.downvote THEN 'comment_downvote' ELSE 'comment_upvote' END; - vote_description := format('Your comment #comment-%s in %s received %s', - NEW."commentId", - service_name, - CASE WHEN NEW.downvote THEN 'a downvote' ELSE 'an upvote' END); - upvote_change := CASE WHEN NEW.downvote THEN -1 ELSE 1 END; -- -1 for downvote, +1 for upvote - ELSIF TG_OP = 'DELETE' THEN - -- Removed vote - karma_points := CASE WHEN OLD.downvote THEN 1 ELSE -1 END; - vote_action := 'comment_vote_removed'; - vote_description := format('A vote was removed from your comment #comment-%s in %s', - OLD."commentId", - service_name); - upvote_change := CASE WHEN OLD.downvote THEN 1 ELSE -1 END; -- +1 if downvote removed, -1 if upvote removed - ELSIF TG_OP = 'UPDATE' THEN - -- Changed vote (from upvote to downvote or vice versa) - karma_points := CASE WHEN NEW.downvote THEN -2 ELSE 2 END; - vote_action := CASE WHEN NEW.downvote THEN 'comment_downvote' ELSE 'comment_upvote' END; - vote_description := format('Your comment #comment-%s in %s vote changed to %s', - NEW."commentId", - service_name, - CASE WHEN NEW.downvote THEN 'downvote' ELSE 'upvote' END); - upvote_change := CASE WHEN NEW.downvote THEN -2 ELSE 2 END; -- -2 if upvote->downvote, +2 if downvote->upvote - END IF; - - -- Record karma transaction and update user karma - PERFORM insert_karma_transaction( - comment_author_id, - karma_points, - vote_action, - COALESCE(NEW."commentId", OLD."commentId"), - vote_description - ); - - PERFORM update_user_karma(comment_author_id, karma_points); - - -- Update comment's upvotes count incrementally - UPDATE "Comment" - SET upvotes = upvotes + upvote_change - WHERE id = COALESCE(NEW."commentId", OLD."commentId"); - - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - --- Main function for handling status changes -CREATE OR REPLACE FUNCTION handle_comment_status_change() -RETURNS TRIGGER AS $$ -BEGIN - PERFORM handle_comment_approval(NEW, OLD); - PERFORM handle_comment_verification(NEW, OLD); - PERFORM handle_comment_spam_status(NEW, OLD); - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - --- Create triggers -CREATE TRIGGER comment_status_change_trigger - AFTER UPDATE OF status - ON "Comment" - FOR EACH ROW - EXECUTE FUNCTION handle_comment_status_change(); - -CREATE TRIGGER comment_suspicious_change_trigger - AFTER UPDATE OF suspicious - ON "Comment" - FOR EACH ROW - EXECUTE FUNCTION handle_comment_status_change(); - -CREATE TRIGGER comment_vote_change_trigger - AFTER INSERT OR UPDATE OR DELETE - ON "CommentVote" - FOR EACH ROW - EXECUTE FUNCTION handle_comment_vote_change(); - --- Function to handle suggestion status changes and award karma -CREATE OR REPLACE FUNCTION handle_suggestion_status_change() -RETURNS TRIGGER AS $$ -DECLARE - service_name TEXT; -BEGIN - -- Award karma for first approval - -- Check that OLD.status is not NULL to handle the initial creation case if needed, - -- and ensure it wasn't already APPROVED. - IF OLD.status IS DISTINCT FROM 'APPROVED' AND NEW.status = 'APPROVED' THEN - -- Fetch service name for the description - SELECT name INTO service_name FROM "Service" WHERE id = NEW."serviceId"; - - -- Insert karma transaction, linking it to the suggestion - PERFORM insert_karma_transaction( - NEW."userId", - 10, - 'suggestion_approved', - NULL, -- p_comment_id (not applicable) - format('Your suggestion for service ''%s'' has been approved!', service_name), - NEW.id -- p_suggestion_id - ); - - -- Update user's total karma - PERFORM update_user_karma(NEW."userId", 10); - END IF; - - RETURN NEW; -- Result is ignored since this is an AFTER trigger -END; -$$ LANGUAGE plpgsql; - --- Create triggers -CREATE TRIGGER suggestion_status_change_trigger - AFTER UPDATE OF status - ON "ServiceSuggestion" - FOR EACH ROW - EXECUTE FUNCTION handle_suggestion_status_change(); diff --git a/web/prisma/triggers/02_service_score.sql b/web/prisma/triggers/02_service_score.sql deleted file mode 100644 index 8754201..0000000 --- a/web/prisma/triggers/02_service_score.sql +++ /dev/null @@ -1,264 +0,0 @@ --- This script defines PostgreSQL functions and triggers for managing service scores: --- 1. Automatically calculates and updates privacy, trust, and overall scores --- for services when services or their attributes change. --- 2. Updates the isRecentlyListed flag for services listed within the last 15 days. --- 3. Queues asynchronous score recalculation in "ServiceScoreRecalculationJob" --- when an "Attribute" definition (e.g., points) is updated, ensuring --- efficient handling of widespread score updates. - --- Drop existing triggers first -DROP TRIGGER IF EXISTS service_score_update_trigger ON "Service"; -DROP TRIGGER IF EXISTS service_attribute_change_trigger ON "ServiceAttribute"; -DROP TRIGGER IF EXISTS attribute_change_trigger ON "Attribute"; - --- Drop existing functions -DROP FUNCTION IF EXISTS calculate_service_scores(); -DROP FUNCTION IF EXISTS calculate_privacy_score(); -DROP FUNCTION IF EXISTS calculate_trust_score(); -DROP FUNCTION IF EXISTS calculate_overall_score(); -DROP FUNCTION IF EXISTS recalculate_scores_for_attribute(); - --- Calculate privacy score based on service attributes and properties -CREATE OR REPLACE FUNCTION calculate_privacy_score(service_id INT) -RETURNS INT AS $$ -DECLARE - privacy_score INT := 50; -- Start from middle value (50) - kyc_factor INT; - onion_factor INT := 0; - i2p_factor INT := 0; - monero_factor INT := 0; - open_source_factor INT := 0; - p2p_factor INT := 0; - decentralized_factor INT := 0; - attributes_score INT := 0; -BEGIN - -- Get service data - SELECT - CASE - WHEN "kycLevel" = 0 THEN 25 -- No KYC is best for privacy - WHEN "kycLevel" = 1 THEN 10 -- Minimal KYC - WHEN "kycLevel" = 2 THEN -5 -- Moderate KYC - WHEN "kycLevel" = 3 THEN -15 -- More KYC - WHEN "kycLevel" = 4 THEN -25 -- Full mandatory KYC - ELSE 0 -- Default to no change - END - INTO kyc_factor - FROM "Service" - WHERE "id" = service_id; - - -- Check for onion URLs - IF EXISTS ( - SELECT 1 FROM "Service" - WHERE "id" = service_id AND array_length("onionUrls", 1) > 0 - ) THEN - onion_factor := 5; - END IF; - - -- Check for i2p URLs - IF EXISTS ( - SELECT 1 FROM "Service" - WHERE "id" = service_id AND array_length("i2pUrls", 1) > 0 - ) THEN - i2p_factor := 5; - END IF; - - -- Check for Monero acceptance - IF EXISTS ( - SELECT 1 FROM "Service" - WHERE "id" = service_id AND 'MONERO' = ANY("acceptedCurrencies") - ) THEN - monero_factor := 5; - END IF; - - -- Calculate score from privacy attributes - directly use the points - SELECT COALESCE(SUM(a."privacyPoints"), 0) - INTO attributes_score - FROM "ServiceAttribute" sa - JOIN "Attribute" a ON sa."attributeId" = a."id" - WHERE sa."serviceId" = service_id AND a."category" = 'PRIVACY'; - - -- Calculate final privacy score (base 100) - privacy_score := privacy_score + kyc_factor + onion_factor + i2p_factor + monero_factor + open_source_factor + p2p_factor + decentralized_factor + attributes_score; - - -- Ensure the score is in reasonable bounds (0-100) - privacy_score := GREATEST(0, LEAST(100, privacy_score)); - - RETURN privacy_score; -END; -$$ LANGUAGE plpgsql; - --- Calculate trust score based on service attributes and verification status -CREATE OR REPLACE FUNCTION calculate_trust_score(service_id INT) -RETURNS INT AS $$ -DECLARE - trust_score INT := 50; -- Start from middle value (50) - verification_factor INT; - attributes_score INT := 0; -BEGIN - -- Get verification status factor - SELECT - CASE - WHEN "verificationStatus" = 'VERIFICATION_SUCCESS' THEN 10 - WHEN "verificationStatus" = 'APPROVED' THEN 5 - WHEN "verificationStatus" = 'COMMUNITY_CONTRIBUTED' THEN 0 - WHEN "verificationStatus" = 'VERIFICATION_FAILED' THEN -50 - ELSE 0 - END - INTO verification_factor - FROM "Service" - WHERE id = service_id; - - -- Calculate score from trust attributes - directly use the points - SELECT COALESCE(SUM(a."trustPoints"), 0) - INTO attributes_score - FROM "ServiceAttribute" sa - JOIN "Attribute" a ON sa."attributeId" = a.id - WHERE sa."serviceId" = service_id AND a.category = 'TRUST'; - - -- Apply penalty if service was listed within the last 15 days - IF EXISTS ( - SELECT 1 - FROM "Service" - WHERE id = service_id - AND "listedAt" IS NOT NULL - AND "verificationStatus" = 'APPROVED' - AND (NOW() - "listedAt") <= INTERVAL '15 days' - ) THEN - trust_score := trust_score - 10; - -- Update the isRecentlyListed flag to true - UPDATE "Service" - SET "isRecentlyListed" = TRUE - WHERE id = service_id; - ELSE - -- Update the isRecentlyListed flag to false - UPDATE "Service" - SET "isRecentlyListed" = FALSE - WHERE id = service_id; - END IF; - - -- Calculate final trust score (base 100) - trust_score := trust_score + verification_factor + attributes_score; - - -- Ensure the score is in reasonable bounds (0-100) - trust_score := GREATEST(0, LEAST(100, trust_score)); - - RETURN trust_score; -END; -$$ LANGUAGE plpgsql; - --- Calculate overall score based on weighted average of privacy and trust scores -CREATE OR REPLACE FUNCTION calculate_overall_score(service_id INT, privacy_score INT, trust_score INT) -RETURNS INT AS $$ -DECLARE - overall_score INT; -BEGIN - overall_score := CAST(ROUND(((privacy_score * 0.6) + (trust_score * 0.4)) / 10.0) AS INT); - RETURN GREATEST(0, LEAST(10, overall_score)); -END; -$$ LANGUAGE plpgsql; - --- Main function to calculate all scores for a service -CREATE OR REPLACE FUNCTION calculate_service_scores() -RETURNS TRIGGER AS $$ -DECLARE - privacy_score INT; - trust_score INT; - overall_score INT; - service_id INT; -BEGIN - -- Determine which service ID to use based on the trigger context and table - IF TG_TABLE_NAME = 'Service' THEN - IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN - service_id := NEW."id"; - END IF; - ELSIF TG_TABLE_NAME = 'ServiceAttribute' THEN - IF TG_OP = 'DELETE' THEN - service_id := OLD."serviceId"; - ELSE -- INSERT or UPDATE - service_id := NEW."serviceId"; - END IF; - END IF; - - -- Calculate each score - privacy_score := calculate_privacy_score(service_id); - trust_score := calculate_trust_score(service_id); - overall_score := calculate_overall_score(service_id, privacy_score, trust_score); - - -- Cap score if service is flagged as scam (verificationStatus = 'VERIFICATION_FAILED') - IF (SELECT "verificationStatus" FROM "Service" WHERE "id" = service_id) = 'VERIFICATION_FAILED' THEN - IF overall_score > 3 THEN - overall_score := 3; - ELSIF overall_score < 0 THEN - overall_score := 0; - END IF; - END IF; - - -- Update the service with the new scores - UPDATE "Service" - SET - "privacyScore" = privacy_score, - "trustScore" = trust_score, - "overallScore" = overall_score - WHERE "id" = service_id; - - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - --- Create trigger to recalculate scores when service is created or updated -CREATE TRIGGER service_score_update_trigger - AFTER INSERT OR UPDATE - ON "Service" - FOR EACH ROW - WHEN (pg_trigger_depth() < 2) -- Prevent recursive triggering - EXECUTE FUNCTION calculate_service_scores(); - --- Create trigger to recalculate scores when service attributes change -CREATE TRIGGER service_attribute_change_trigger - AFTER INSERT OR UPDATE OR DELETE - ON "ServiceAttribute" - FOR EACH ROW - WHEN (pg_trigger_depth() < 2) -- Prevent recursive triggering - EXECUTE FUNCTION calculate_service_scores(); - --- Function to queue score recalculation for all services with a specific attribute -CREATE OR REPLACE FUNCTION queue_service_score_recalculation_for_attribute() -RETURNS TRIGGER AS $$ -DECLARE - service_rec RECORD; -BEGIN - -- Only trigger recalculation if relevant fields changed - IF (TG_OP = 'UPDATE' AND ( - OLD."privacyPoints" != NEW."privacyPoints" OR - OLD."trustPoints" != NEW."trustPoints" OR - OLD."type" != NEW."type" OR - OLD."category" != NEW."category" - )) THEN - -- Find all services that have this attribute and queue a recalculation job - FOR service_rec IN - SELECT DISTINCT sa."serviceId" - FROM "ServiceAttribute" sa - WHERE sa."attributeId" = NEW.id - LOOP - -- Insert a job into the queue table - -- ON CONFLICT clause ensures we don't queue the same service multiple times per transaction - INSERT INTO "ServiceScoreRecalculationJob" ("serviceId", "createdAt", "processedAt") - VALUES (service_rec."serviceId", NOW(), NULL) - ON CONFLICT ("serviceId") DO UPDATE SET "processedAt" = NULL, "createdAt" = NOW(); - - END LOOP; - END IF; - - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - --- Create constraint trigger to queue score recalculation when attributes are updated -DROP TRIGGER IF EXISTS attribute_change_trigger ON "Attribute"; -CREATE CONSTRAINT TRIGGER attribute_change_trigger - AFTER UPDATE - ON "Attribute" - DEFERRABLE INITIALLY DEFERRED - FOR EACH ROW - WHEN (pg_trigger_depth() < 2) - EXECUTE FUNCTION queue_service_score_recalculation_for_attribute(); diff --git a/web/prisma/triggers/03_service_user_rating.sql b/web/prisma/triggers/03_service_user_rating.sql deleted file mode 100644 index e49fec9..0000000 --- a/web/prisma/triggers/03_service_user_rating.sql +++ /dev/null @@ -1,57 +0,0 @@ --- This script defines a PostgreSQL function and trigger to automatically calculate --- and update the average user rating for services based on associated comments. --- The average rating is recalculated whenever comments are added, updated, or deleted. - --- Drop existing triggers first -DROP TRIGGER IF EXISTS comment_average_rating_trigger ON "Comment"; - --- Drop existing functions -DROP FUNCTION IF EXISTS calculate_average_rating(); - --- Calculate average rating based on active comments with approved or verified status -CREATE OR REPLACE FUNCTION calculate_average_rating() -RETURNS TRIGGER AS $$ -DECLARE - affected_service_id INT; - average_user_rating DECIMAL; -BEGIN - -- Determine which service ID to use based on the trigger context - IF TG_OP = 'DELETE' THEN - affected_service_id := OLD."serviceId"; - ELSE -- INSERT or UPDATE - affected_service_id := NEW."serviceId"; - END IF; - - -- Calculate average rating from active comments with approved or verified status - -- Excluding suspicious comments and replies (comments with parentId not null) - - SELECT AVG(rating) INTO average_user_rating - FROM "Comment" - WHERE "serviceId" = affected_service_id - AND "parentId" IS NULL - AND rating IS NOT NULL - AND (status = 'APPROVED' OR status = 'VERIFIED') - AND "ratingActive" = true - AND suspicious = false; - - -- Update the service with the new average rating - UPDATE "Service" - SET "averageUserRating" = average_user_rating - WHERE "id" = affected_service_id; - - -- Return the appropriate record based on operation - IF TG_OP = 'DELETE' THEN - RETURN OLD; - ELSE - RETURN NEW; - END IF; -END; -$$ LANGUAGE plpgsql; - --- Create trigger to recalculate average rating when comments are created, updated, or deleted -CREATE TRIGGER comment_average_rating_trigger - AFTER INSERT OR UPDATE OR DELETE - ON "Comment" - FOR EACH ROW - WHEN (pg_trigger_depth() < 2) -- Prevent recursive triggering - EXECUTE FUNCTION calculate_average_rating(); \ No newline at end of file diff --git a/web/prisma/triggers/04_service_verification_status.sql b/web/prisma/triggers/04_service_verification_status.sql deleted file mode 100644 index c22e900..0000000 --- a/web/prisma/triggers/04_service_verification_status.sql +++ /dev/null @@ -1,48 +0,0 @@ --- This script manages the `listedAt`, `verifiedAt`, and `isRecentlyListed` timestamps --- for services based on changes to their `verificationStatus`. It ensures these timestamps --- are set or cleared appropriately when a service's verification status is updated. - -CREATE OR REPLACE FUNCTION manage_service_timestamps() -RETURNS TRIGGER AS $$ -BEGIN - -- Manage listedAt timestamp - IF NEW."verificationStatus" IN ('APPROVED', 'VERIFICATION_SUCCESS') THEN - -- Set listedAt only on the first time status becomes APPROVED or VERIFICATION_SUCCESS - IF OLD."listedAt" IS NULL THEN - NEW."listedAt" := NOW(); - NEW."isRecentlyListed" := TRUE; - END IF; - ELSIF OLD."verificationStatus" IN ('APPROVED', 'VERIFICATION_SUCCESS') THEN - -- Clear listedAt if the status changes FROM APPROVED or VERIFICATION_SUCCESS to something else - -- The trigger's WHEN clause ensures NEW."verificationStatus" is different. - NEW."listedAt" := NULL; - NEW."isRecentlyListed" := FALSE; - END IF; - - -- Manage verifiedAt timestamp - IF NEW."verificationStatus" = 'VERIFICATION_SUCCESS' THEN - -- Set verifiedAt when status changes TO VERIFICATION_SUCCESS - NEW."verifiedAt" := NOW(); - NEW."isRecentlyListed" := FALSE; - ELSIF OLD."verificationStatus" = 'VERIFICATION_SUCCESS' THEN - -- Clear verifiedAt when status changes FROM VERIFICATION_SUCCESS - -- The trigger's WHEN clause ensures NEW."verificationStatus" is different. - NEW."verifiedAt" := NULL; - NEW."isRecentlyListed" := FALSE; - END IF; - - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - --- Drop the old trigger first if it exists under the old name -DROP TRIGGER IF EXISTS trigger_set_service_listed_at ON "Service"; --- Drop the trigger if it exists under the new name -DROP TRIGGER IF EXISTS trigger_manage_service_timestamps ON "Service"; - -CREATE TRIGGER trigger_manage_service_timestamps -BEFORE UPDATE OF "verificationStatus" ON "Service" -FOR EACH ROW --- Only execute the function if the verificationStatus value has actually changed -WHEN (OLD."verificationStatus" IS DISTINCT FROM NEW."verificationStatus") -EXECUTE FUNCTION manage_service_timestamps(); diff --git a/web/prisma/triggers/05_service_events.sql b/web/prisma/triggers/05_service_events.sql deleted file mode 100644 index 77b01b2..0000000 --- a/web/prisma/triggers/05_service_events.sql +++ /dev/null @@ -1,399 +0,0 @@ --- Service Events Trigger --- This trigger automatically creates events when services are updated --- to track important changes over time - -CREATE OR REPLACE FUNCTION trigger_service_events() -RETURNS TRIGGER AS $$ -DECLARE - change_descriptions TEXT[] := '{}'; - event_title TEXT; - event_content TEXT; - change_type TEXT := NULL; - event_time TIMESTAMP WITH TIME ZONE := transaction_timestamp(); - currency_desc TEXT; -BEGIN - -- Only proceed if this is an UPDATE operation - IF TG_OP <> 'UPDATE' THEN - RETURN NEW; - END IF; - - -- Check for domain/URL changes - IF OLD."serviceUrls" IS DISTINCT FROM NEW."serviceUrls" THEN - change_descriptions := array_append(change_descriptions, - 'Service URLs updated from ' || array_to_string(OLD."serviceUrls", ', ') || - ' to ' || array_to_string(NEW."serviceUrls", ', ') - ); - change_type := COALESCE(change_type, 'Domain change'); - END IF; - - -- Check for KYC level changes - IF OLD."kycLevel" IS DISTINCT FROM NEW."kycLevel" THEN - change_descriptions := array_append(change_descriptions, - 'KYC level changed from ' || OLD."kycLevel"::TEXT || ' to ' || NEW."kycLevel"::TEXT - ); - change_type := COALESCE(change_type, 'KYC update'); - END IF; - - -- Check for verification status changes - IF OLD."verificationStatus" IS DISTINCT FROM NEW."verificationStatus" THEN - change_descriptions := array_append(change_descriptions, - 'Verification status changed from ' || OLD."verificationStatus"::TEXT || ' to ' || NEW."verificationStatus"::TEXT - ); - change_type := COALESCE(change_type, 'Verification update'); - END IF; - - -- Check for description changes - IF OLD.description IS DISTINCT FROM NEW.description THEN - change_descriptions := array_append(change_descriptions, 'Description was updated'); - change_type := COALESCE(change_type, 'Description update'); - END IF; - - -- Check for currency changes - IF OLD."acceptedCurrencies" IS DISTINCT FROM NEW."acceptedCurrencies" THEN - -- Find currencies added - WITH - old_currencies AS (SELECT unnest(OLD."acceptedCurrencies") AS currency), - new_currencies AS (SELECT unnest(NEW."acceptedCurrencies") AS currency), - added_currencies AS ( - SELECT currency FROM new_currencies - EXCEPT - SELECT currency FROM old_currencies - ), - removed_currencies AS ( - SELECT currency FROM old_currencies - EXCEPT - SELECT currency FROM new_currencies - ) - - -- Temp variable for currency description - SELECT - CASE - WHEN (SELECT COUNT(*) FROM added_currencies) > 0 AND (SELECT COUNT(*) FROM removed_currencies) > 0 THEN - 'Currencies updated: added ' || array_to_string(ARRAY(SELECT currency FROM added_currencies), ', ') || - ', removed ' || array_to_string(ARRAY(SELECT currency FROM removed_currencies), ', ') - WHEN (SELECT COUNT(*) FROM added_currencies) > 0 THEN - 'Added currencies: ' || array_to_string(ARRAY(SELECT currency FROM added_currencies), ', ') - WHEN (SELECT COUNT(*) FROM removed_currencies) > 0 THEN - 'Removed currencies: ' || array_to_string(ARRAY(SELECT currency FROM removed_currencies), ', ') - ELSE - 'Currencies changed' - END - INTO currency_desc; - - IF currency_desc IS NOT NULL AND currency_desc <> '' THEN - change_descriptions := array_append(change_descriptions, currency_desc); - change_type := COALESCE(change_type, 'Currency update'); - END IF; - END IF; - - -- If there are changes, create an event - IF array_length(change_descriptions, 1) > 0 THEN - -- Create a title based on number of changes - IF array_length(change_descriptions, 1) = 1 THEN - event_title := COALESCE(change_type, 'Service updated'); -- Ensure title is not null - ELSE - event_title := 'Service updated'; - END IF; - - -- Create content with all changes - event_content := array_to_string(change_descriptions, '. '); - - -- Ensure content is not null or empty - IF event_content IS NULL OR event_content = '' THEN - event_content := 'Service details changed (content unavailable)'; - END IF; - - -- Insert the event - INSERT INTO "Event" ( - "title", - "content", - "type", - "visible", - "startedAt", - "endedAt", - "serviceId" - ) VALUES ( - event_title, - event_content, - 'UPDATE', - TRUE, - event_time, - event_time, - NEW.id - ); - END IF; - - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - --- Create a trigger for service updates -DROP TRIGGER IF EXISTS service_events_trigger ON "Service"; -CREATE TRIGGER service_events_trigger -AFTER UPDATE OF "serviceUrls", "kycLevel", "verificationStatus", "description", "acceptedCurrencies" ON "Service" -FOR EACH ROW -EXECUTE FUNCTION trigger_service_events(); - --- Additional trigger to monitor changes to ServiceAttribute -CREATE OR REPLACE FUNCTION trigger_service_attribute_events() -RETURNS TRIGGER AS $$ -DECLARE - attribute_name TEXT; - service_name TEXT; - event_title TEXT := 'Attribute change'; -- Default title - event_content TEXT; - event_time TIMESTAMP WITH TIME ZONE := transaction_timestamp(); - target_service_id INT; - service_exists BOOLEAN; - service_created_at TIMESTAMP WITH TIME ZONE; - is_new_service BOOLEAN := FALSE; -BEGIN - -- Determine target service ID and operation type - IF TG_OP = 'INSERT' THEN - target_service_id := NEW."serviceId"; - - -- Check if this is a new service (created within the last minute) - -- This helps prevent events when attributes are initially added to a new service - SELECT "createdAt" INTO service_created_at FROM "Service" WHERE id = target_service_id; - IF service_created_at IS NOT NULL AND (event_time - service_created_at) < INTERVAL '1 minute' THEN - is_new_service := TRUE; - RETURN NEW; -- Skip event creation for new services - END IF; - - SELECT title INTO attribute_name FROM "Attribute" WHERE id = NEW."attributeId"; - SELECT name INTO service_name FROM "Service" WHERE id = target_service_id; - IF attribute_name IS NOT NULL AND service_name IS NOT NULL THEN - event_title := 'Attribute added'; - event_content := 'Attribute "' || attribute_name || '" was added to ' || service_name; - ELSE - event_content := 'An attribute was added (details unavailable)'; - END IF; - - ELSIF TG_OP = 'DELETE' THEN - target_service_id := OLD."serviceId"; - -- Check if the service still exists before trying to fetch its name or create an event - SELECT EXISTS (SELECT 1 FROM "Service" WHERE id = target_service_id) INTO service_exists; - IF service_exists THEN - SELECT title INTO attribute_name FROM "Attribute" WHERE id = OLD."attributeId"; - SELECT name INTO service_name FROM "Service" WHERE id = target_service_id; - IF attribute_name IS NOT NULL AND service_name IS NOT NULL THEN - event_title := 'Attribute removed'; - event_content := 'Attribute "' || attribute_name || '" was removed from ' || service_name; - ELSE - -- This case might happen if attribute was deleted concurrently - event_content := 'An attribute was removed (details unavailable)'; - END IF; - ELSE - -- Service was deleted, don't create an event - RETURN OLD; - END IF; - END IF; - - -- Ensure content is not null/empty and insert - IF event_content IS NOT NULL AND event_content <> '' AND target_service_id IS NOT NULL AND NOT is_new_service THEN - -- Re-check service existence right before insert just in case of concurrency on INSERT - IF TG_OP = 'INSERT' THEN - SELECT EXISTS (SELECT 1 FROM "Service" WHERE id = target_service_id) INTO service_exists; - END IF; - - IF service_exists THEN - INSERT INTO "Event" ( - "title", - "content", - "type", - "visible", - "startedAt", - "endedAt", - "serviceId" - ) VALUES ( - event_title, - event_content, - 'UPDATE', - TRUE, - event_time, - event_time, - target_service_id - ); - END IF; - END IF; - - -- Return appropriate record - IF TG_OP = 'INSERT' THEN - RETURN NEW; - ELSE - RETURN OLD; - END IF; -END; -$$ LANGUAGE plpgsql; - --- Create a trigger for service attribute changes -DROP TRIGGER IF EXISTS service_attribute_events_trigger ON "ServiceAttribute"; -CREATE TRIGGER service_attribute_events_trigger -AFTER INSERT OR DELETE ON "ServiceAttribute" -FOR EACH ROW -EXECUTE FUNCTION trigger_service_attribute_events(); - --- Additional trigger to monitor changes to service categories -CREATE OR REPLACE FUNCTION trigger_service_category_events() -RETURNS TRIGGER AS $$ -DECLARE - category_name TEXT; - service_name TEXT; - event_title TEXT := 'Category change'; -- Default title - event_content TEXT; - event_time TIMESTAMP WITH TIME ZONE := transaction_timestamp(); - target_service_id INT; - service_exists BOOLEAN; - service_created_at TIMESTAMP WITH TIME ZONE; - is_new_service BOOLEAN := FALSE; -BEGIN - -- Determine target service ID and operation type - IF TG_OP = 'INSERT' THEN - target_service_id := NEW."A"; - - -- Check if this is a new service (created within the last minute) - -- This helps prevent events when categories are initially added to a new service - SELECT "createdAt" INTO service_created_at FROM "Service" WHERE id = target_service_id; - IF service_created_at IS NOT NULL AND (event_time - service_created_at) < INTERVAL '1 minute' THEN - is_new_service := TRUE; - RETURN NEW; -- Skip event creation for new services - END IF; - - SELECT name INTO category_name FROM "Category" WHERE id = NEW."B"; - SELECT name INTO service_name FROM "Service" WHERE id = target_service_id; - IF category_name IS NOT NULL AND service_name IS NOT NULL THEN - event_title := 'Category added'; - event_content := 'Category "' || category_name || '" was added to ' || service_name; - ELSE - event_content := 'A category was added (details unavailable)'; - END IF; - - ELSIF TG_OP = 'DELETE' THEN - target_service_id := OLD."A"; - -- Check if the service still exists before trying to fetch its name or create an event - SELECT EXISTS (SELECT 1 FROM "Service" WHERE id = target_service_id) INTO service_exists; - IF service_exists THEN - SELECT name INTO category_name FROM "Category" WHERE id = OLD."B"; - SELECT name INTO service_name FROM "Service" WHERE id = target_service_id; - IF category_name IS NOT NULL AND service_name IS NOT NULL THEN - event_title := 'Category removed'; - event_content := 'Category "' || category_name || '" was removed from ' || service_name; - ELSE - -- This case might happen if category was deleted concurrently - event_content := 'A category was removed (details unavailable)'; - END IF; - ELSE - -- Service was deleted, don't create an event - RETURN OLD; - END IF; - END IF; - - -- Ensure content is not null/empty and insert - IF event_content IS NOT NULL AND event_content <> '' AND target_service_id IS NOT NULL AND NOT is_new_service THEN - -- Re-check service existence right before insert just in case of concurrency on INSERT - IF TG_OP = 'INSERT' THEN - SELECT EXISTS (SELECT 1 FROM "Service" WHERE id = target_service_id) INTO service_exists; - END IF; - - IF service_exists THEN - INSERT INTO "Event" ( - "title", - "content", - "type", - "visible", - "startedAt", - "endedAt", - "serviceId" - ) VALUES ( - event_title, - event_content, - 'UPDATE', - TRUE, - event_time, - event_time, - target_service_id - ); - END IF; - END IF; - - -- Return appropriate record - IF TG_OP = 'INSERT' THEN - RETURN NEW; - ELSE - RETURN OLD; - END IF; -END; -$$ LANGUAGE plpgsql; - --- Create a trigger for service category changes (on the junction table) -DROP TRIGGER IF EXISTS service_category_events_trigger ON "_ServiceToCategory"; -CREATE TRIGGER service_category_events_trigger -AFTER INSERT OR DELETE ON "_ServiceToCategory" -FOR EACH ROW -EXECUTE FUNCTION trigger_service_category_events(); - --- Verification Steps Trigger --- This trigger creates events when verification steps are added or status changes -CREATE OR REPLACE FUNCTION trigger_verification_step_events() -RETURNS TRIGGER AS $$ -DECLARE - service_name TEXT; - event_title TEXT; - event_content TEXT; - event_time TIMESTAMP WITH TIME ZONE := transaction_timestamp(); - service_exists BOOLEAN; -BEGIN - -- Check if the service exists - SELECT EXISTS (SELECT 1 FROM "Service" WHERE id = NEW."serviceId") INTO service_exists; - - IF NOT service_exists THEN - -- Service was deleted or doesn't exist, don't create an event - RETURN NEW; - END IF; - - -- Get service name - SELECT name INTO service_name FROM "Service" WHERE id = NEW."serviceId"; - - -- Handle different operations - IF TG_OP = 'INSERT' THEN - event_title := 'Verification step added'; - event_content := '"' || NEW.title || '" was added'; - - ELSIF TG_OP = 'UPDATE' AND OLD.status IS DISTINCT FROM NEW.status THEN - event_title := 'Verification step ' || replace(lower(NEW.status::TEXT), '_', ' '); - event_content := '"' || NEW.title || '" status changed from ' || - replace(lower(OLD.status::TEXT), '_', ' ') || ' to ' || replace(lower(NEW.status::TEXT), '_', ' '); - ELSE - -- No relevant changes, exit - RETURN NEW; - END IF; - - -- Insert the event - INSERT INTO "Event" ( - "title", - "content", - "type", - "visible", - "startedAt", - "endedAt", - "serviceId" - ) VALUES ( - event_title, - event_content, - 'UPDATE', - TRUE, - event_time, - event_time, - NEW."serviceId" - ); - - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - --- Create trigger for verification step changes -DROP TRIGGER IF EXISTS verification_step_events_trigger ON "VerificationStep"; -CREATE TRIGGER verification_step_events_trigger -AFTER INSERT OR UPDATE OF status ON "VerificationStep" -FOR EACH ROW -EXECUTE FUNCTION trigger_verification_step_events(); diff --git a/web/prisma/triggers/06_notifications_comments.sql b/web/prisma/triggers/06_notifications_comments.sql deleted file mode 100644 index 6c5acee..0000000 --- a/web/prisma/triggers/06_notifications_comments.sql +++ /dev/null @@ -1,227 +0,0 @@ --- Function & Trigger for Root Comment Insertions (Approved/Verified) -CREATE OR REPLACE FUNCTION notify_root_comment_inserted() -RETURNS TRIGGER AS $$ -DECLARE - watcher_count INT; -BEGIN - RAISE NOTICE '[notify_root_comment_inserted] Trigger fired for comment ID: %', NEW.id; - WITH watchers AS ( - SELECT np."userId", np."enableNotifyPendingRepliesOnWatch" - FROM "_onRootCommentCreatedForServices" rc - JOIN "NotificationPreferences" np ON rc."A" = np."id" - WHERE rc."B" = NEW."serviceId" - AND np."userId" <> NEW."authorId" - ) - INSERT INTO "Notification" ("userId", "type", "aboutCommentId") - SELECT w."userId", - 'ROOT_COMMENT_CREATED', - NEW."id" - FROM watchers w - WHERE ( - NEW.status IN ('APPROVED', 'VERIFIED') - OR (NEW.status = 'PENDING' AND w."enableNotifyPendingRepliesOnWatch") - ) - ON CONFLICT DO NOTHING; - - RAISE NOTICE '[notify_root_comment_inserted] Inserted % notifications for comment ID: %', FOUND, NEW.id; - - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - -DROP TRIGGER IF EXISTS trg_notify_root_comment_inserted ON "Comment"; -CREATE TRIGGER trg_notify_root_comment_inserted - AFTER INSERT ON "Comment" - FOR EACH ROW - WHEN (NEW."parentId" IS NULL) - EXECUTE FUNCTION notify_root_comment_inserted(); - --- Function & Trigger for Reply Comment Insertions -CREATE OR REPLACE FUNCTION notify_reply_comment_inserted() -RETURNS TRIGGER AS $$ -BEGIN - WITH watchers AS ( - SELECT np."userId", np."enableNotifyPendingRepliesOnWatch" - FROM "_watchedComments" w - JOIN "NotificationPreferences" np ON w."B" = np."id" - WHERE w."A" = NEW."parentId" - AND np."userId" <> NEW."authorId" - ) - INSERT INTO "Notification" ("userId", "type", "aboutCommentId") - SELECT w."userId", - 'REPLY_COMMENT_CREATED', - NEW."id" - FROM watchers w - WHERE ( - NEW.status IN ('APPROVED', 'VERIFIED') - OR (NEW.status = 'PENDING' AND w."enableNotifyPendingRepliesOnWatch") - ) - ON CONFLICT DO NOTHING; - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - -DROP TRIGGER IF EXISTS trg_notify_reply_comment_inserted ON "Comment"; -CREATE TRIGGER trg_notify_reply_comment_inserted - AFTER INSERT ON "Comment" - FOR EACH ROW - WHEN (NEW."parentId" IS NOT NULL) - EXECUTE FUNCTION notify_reply_comment_inserted(); - --- Function & Trigger for Reply Approval/Verification -CREATE OR REPLACE FUNCTION notify_reply_approved() -RETURNS TRIGGER AS $$ -BEGIN - WITH watchers AS ( - SELECT np."userId" - FROM "_watchedComments" w - JOIN "NotificationPreferences" np ON w."B" = np."id" - WHERE w."A" = NEW."parentId" - AND np."userId" <> NEW."authorId" - ) - INSERT INTO "Notification" ("userId", "type", "aboutCommentId") - SELECT w."userId", - 'REPLY_COMMENT_CREATED', - NEW."id" - FROM watchers w - ON CONFLICT DO NOTHING; - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - -DROP TRIGGER IF EXISTS trg_notify_reply_approved ON "Comment"; -CREATE TRIGGER trg_notify_reply_approved - AFTER UPDATE OF status ON "Comment" - FOR EACH ROW - WHEN (NEW."parentId" IS NOT NULL AND NEW.status IN ('APPROVED', 'VERIFIED') AND OLD.status NOT IN ('APPROVED', 'VERIFIED')) - EXECUTE FUNCTION notify_reply_approved(); - -DROP TRIGGER IF EXISTS trg_notify_root_approved ON "Comment"; - -CREATE OR REPLACE FUNCTION notify_root_approved() -RETURNS TRIGGER AS $$ -BEGIN - WITH watchers AS ( - SELECT np."userId" - FROM "_onRootCommentCreatedForServices" rc - JOIN "NotificationPreferences" np ON rc."A" = np."id" - WHERE rc."B" = NEW."serviceId" - AND np."userId" <> NEW."authorId" - AND NOT np."enableNotifyPendingRepliesOnWatch" - ) - INSERT INTO "Notification" ("userId", "type", "aboutCommentId") - SELECT w."userId", - 'ROOT_COMMENT_CREATED', - NEW."id" - FROM watchers w - ON CONFLICT DO NOTHING; - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - -CREATE TRIGGER trg_notify_root_approved - AFTER UPDATE OF status ON "Comment" - FOR EACH ROW - WHEN (NEW."parentId" IS NULL AND NEW.status IN ('APPROVED', 'VERIFIED') AND OLD.status NOT IN ('APPROVED', 'VERIFIED')) - EXECUTE FUNCTION notify_root_approved(); - --- Function & Trigger for Comment Status Changes (Status, Suspicious, AdminReview) -CREATE OR REPLACE FUNCTION notify_comment_status_changed() -RETURNS TRIGGER AS $$ -DECLARE - v_status_change "CommentStatusChange" := NULL; -BEGIN - -- Determine the status change type - IF NEW.status <> OLD.status THEN - IF NEW.status = 'APPROVED' THEN v_status_change := 'STATUS_CHANGED_TO_APPROVED'; - ELSIF NEW.status = 'VERIFIED' THEN v_status_change := 'STATUS_CHANGED_TO_VERIFIED'; - ELSIF NEW.status = 'REJECTED' THEN v_status_change := 'STATUS_CHANGED_TO_REJECTED'; - ELSIF (NEW.status = 'PENDING' OR NEW.status = 'HUMAN_PENDING') AND (OLD.status <> 'PENDING' AND OLD.status <> 'HUMAN_PENDING') THEN v_status_change := 'STATUS_CHANGED_TO_PENDING'; - END IF; - ELSIF NEW.suspicious <> OLD.suspicious THEN - IF NEW.suspicious = true THEN v_status_change := 'MARKED_AS_SPAM'; - ELSE v_status_change := 'UNMARKED_AS_SPAM'; - END IF; - ELSIF NEW."requiresAdminReview" <> OLD."requiresAdminReview" THEN - IF NEW."requiresAdminReview" = true THEN v_status_change := 'MARKED_FOR_ADMIN_REVIEW'; - ELSE v_status_change := 'UNMARKED_FOR_ADMIN_REVIEW'; - END IF; - END IF; - - -- If a relevant status change occurred, notify watchers of THIS comment - IF v_status_change IS NOT NULL THEN - WITH watchers AS ( - -- Get all watchers excluding author - SELECT np."userId" - FROM "_watchedComments" w - JOIN "NotificationPreferences" np ON w."B" = np."id" - WHERE w."A" = NEW."id" - AND np."userId" <> NEW."authorId" - AND np."enableOnMyCommentStatusChange" - - UNION ALL - - -- Add author if they have enabled notifications for their own comments - SELECT np."userId" - FROM "NotificationPreferences" np - WHERE np."userId" = NEW."authorId" - AND np."enableOnMyCommentStatusChange" - ) - INSERT INTO "Notification" ("userId", "type", "aboutCommentId", "aboutCommentStatusChange") - SELECT w."userId", - 'COMMENT_STATUS_CHANGE', - NEW."id", - v_status_change - FROM watchers w - ON CONFLICT DO NOTHING; - END IF; - - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - -DROP TRIGGER IF EXISTS trg_notify_comment_status_changed ON "Comment"; -CREATE TRIGGER trg_notify_comment_status_changed - AFTER UPDATE OF status, suspicious, "requiresAdminReview" ON "Comment" - FOR EACH ROW - WHEN (NEW.status <> OLD.status OR NEW.suspicious <> OLD.suspicious OR NEW."requiresAdminReview" <> OLD."requiresAdminReview") - EXECUTE FUNCTION notify_comment_status_changed(); - --- Function & Trigger for Community Note Added -CREATE OR REPLACE FUNCTION notify_community_note_added() -RETURNS TRIGGER AS $$ -BEGIN - -- Notify watchers of this specific comment (excluding author) - WITH watchers AS ( - SELECT np."userId" - FROM "_watchedComments" w - JOIN "NotificationPreferences" np ON w."B" = np."id" - WHERE w."A" = NEW."id" - AND np."userId" <> NEW."authorId" - ) - INSERT INTO "Notification" ("userId", "type", "aboutCommentId") - SELECT w."userId", - 'COMMUNITY_NOTE_ADDED', - NEW."id" - FROM watchers w - ON CONFLICT DO NOTHING; - - -- Always notify the author - INSERT INTO "Notification" ("userId", "type", "aboutCommentId") - VALUES (NEW."authorId", 'COMMUNITY_NOTE_ADDED', NEW."id") - ON CONFLICT DO NOTHING; - - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - -DROP TRIGGER IF EXISTS trg_notify_community_note_added ON "Comment"; -CREATE TRIGGER trg_notify_community_note_added - AFTER UPDATE OF "communityNote" ON "Comment" - FOR EACH ROW - WHEN (NEW."communityNote" IS NOT NULL AND NEW."communityNote" <> '' AND (OLD."communityNote" IS NULL OR OLD."communityNote" = '')) - EXECUTE FUNCTION notify_community_note_added(); - --- Remove the old monolithic trigger and function definition if they still exist -DROP TRIGGER IF EXISTS comment_notifications_trigger ON "Comment"; -DROP FUNCTION IF EXISTS trigger_comment_notifications(); diff --git a/web/prisma/triggers/07_notifications_service_suggestion.sql b/web/prisma/triggers/07_notifications_service_suggestion.sql deleted file mode 100644 index 4da39fa..0000000 --- a/web/prisma/triggers/07_notifications_service_suggestion.sql +++ /dev/null @@ -1,72 +0,0 @@ -CREATE OR REPLACE FUNCTION trigger_service_suggestion_notifications() -RETURNS TRIGGER AS $$ -DECLARE - suggestion_status_change "ServiceSuggestionStatusChange"; -BEGIN - IF TG_OP = 'INSERT' THEN -- Corresponds to ServiceSuggestionMessage insert - -- Notify suggestion author (if not the sender) - INSERT INTO "Notification" ("userId", "type", "aboutServiceSuggestionId", "aboutServiceSuggestionMessageId") - SELECT s."userId", 'SUGGESTION_MESSAGE', NEW."suggestionId", NEW."id" - FROM "ServiceSuggestion" s - WHERE s."id" = NEW."suggestionId" - AND s."userId" <> NEW."userId" - AND NOT EXISTS ( - SELECT 1 FROM "Notification" n - WHERE n."userId" = s."userId" - AND n."type" = 'SUGGESTION_MESSAGE' - AND n."aboutServiceSuggestionMessageId" = NEW."id" - ); - - -- Notify all admins (except the sender), but only if sender is not admin - INSERT INTO "Notification" ("userId", "type", "aboutServiceSuggestionId", "aboutServiceSuggestionMessageId") - SELECT u."id", 'SUGGESTION_MESSAGE', NEW."suggestionId", NEW."id" - FROM "User" u - WHERE u."admin" = true - AND u."id" <> NEW."userId" - -- Only notify admins if the message sender is not an admin - AND NOT EXISTS (SELECT 1 FROM "User" WHERE "id" = NEW."userId" AND "admin" = true) - AND NOT EXISTS ( - SELECT 1 FROM "Notification" n - WHERE n."userId" = u."id" - AND n."type" = 'SUGGESTION_MESSAGE' - AND n."aboutServiceSuggestionMessageId" = NEW."id" - ); - - ELSIF TG_OP = 'UPDATE' THEN -- Corresponds to ServiceSuggestion status update - -- Notify suggestion author about status change - IF NEW.status <> OLD.status THEN - IF NEW.status = 'PENDING' THEN - suggestion_status_change := 'STATUS_CHANGED_TO_PENDING'; - ELSIF NEW.status = 'APPROVED' THEN - suggestion_status_change := 'STATUS_CHANGED_TO_APPROVED'; - ELSIF NEW.status = 'REJECTED' THEN - suggestion_status_change := 'STATUS_CHANGED_TO_REJECTED'; - ELSIF NEW.status = 'WITHDRAWN' THEN - suggestion_status_change := 'STATUS_CHANGED_TO_WITHDRAWN'; - END IF; - - INSERT INTO "Notification" ("userId", "type", "aboutServiceSuggestionId", "aboutSuggestionStatusChange") - VALUES (NEW."userId", 'SUGGESTION_STATUS_CHANGE', NEW."id", suggestion_status_change); - END IF; - END IF; - - -- Use RETURN NULL for AFTER triggers as the return value is ignored. - RETURN NULL; -END; -$$ LANGUAGE plpgsql; - --- Trigger for new messages -DROP TRIGGER IF EXISTS service_suggestion_message_notifications_trigger ON "ServiceSuggestionMessage"; -CREATE TRIGGER service_suggestion_message_notifications_trigger - AFTER INSERT ON "ServiceSuggestionMessage" - FOR EACH ROW - EXECUTE FUNCTION trigger_service_suggestion_notifications(); - --- Trigger for status updates -DROP TRIGGER IF EXISTS service_suggestion_status_notifications_trigger ON "ServiceSuggestion"; -CREATE TRIGGER service_suggestion_status_notifications_trigger - AFTER UPDATE OF status ON "ServiceSuggestion" - FOR EACH ROW - -- Only run the function if the status actually changed - WHEN (OLD.status IS DISTINCT FROM NEW.status) - EXECUTE FUNCTION trigger_service_suggestion_notifications(); diff --git a/web/prisma/triggers/08_notifications_service_events.sql b/web/prisma/triggers/08_notifications_service_events.sql deleted file mode 100644 index c003e49..0000000 --- a/web/prisma/triggers/08_notifications_service_events.sql +++ /dev/null @@ -1,28 +0,0 @@ -CREATE OR REPLACE FUNCTION trigger_service_events_notifications() -RETURNS TRIGGER AS $$ -BEGIN - -- Handle new Event insertions - IF TG_TABLE_NAME = 'Event' AND TG_OP = 'INSERT' THEN - INSERT INTO "Notification" ("userId", "type", "aboutServiceId", "aboutEventId") - SELECT np."userId", 'EVENT_CREATED', NEW."serviceId", NEW.id - FROM "_onEventCreatedForServices" oes - JOIN "NotificationPreferences" np ON oes."A" = np.id - WHERE oes."B" = NEW."serviceId" - AND NOT EXISTS ( - SELECT 1 FROM "Notification" n - WHERE n."userId" = np."userId" - AND n."type" = 'EVENT_CREATED' - AND n."aboutEventId" = NEW.id - ); - END IF; - - RETURN NULL; -END; -$$ LANGUAGE plpgsql; - --- Trigger for new Events -DROP TRIGGER IF EXISTS eVENT_CREATED_notifications_trigger ON "Event"; -CREATE TRIGGER eVENT_CREATED_notifications_trigger - AFTER INSERT ON "Event" - FOR EACH ROW - EXECUTE FUNCTION trigger_service_events_notifications(); diff --git a/web/prisma/triggers/09_notifications_service_status_updates.sql b/web/prisma/triggers/09_notifications_service_status_updates.sql deleted file mode 100644 index 16655c9..0000000 --- a/web/prisma/triggers/09_notifications_service_status_updates.sql +++ /dev/null @@ -1,37 +0,0 @@ -CREATE OR REPLACE FUNCTION trigger_service_verification_status_change_notifications() -RETURNS TRIGGER AS $$ -DECLARE - v_status_change "ServiceVerificationStatusChange"; -BEGIN - -- Check if verificationStatus actually changed - IF OLD."verificationStatus" IS DISTINCT FROM NEW."verificationStatus" THEN - -- Determine the correct ServiceVerificationStatusChange enum value - SELECT CASE NEW."verificationStatus" - WHEN 'COMMUNITY_CONTRIBUTED' THEN 'STATUS_CHANGED_TO_COMMUNITY_CONTRIBUTED' - WHEN 'APPROVED' THEN 'STATUS_CHANGED_TO_APPROVED' - WHEN 'VERIFICATION_SUCCESS' THEN 'STATUS_CHANGED_TO_VERIFICATION_SUCCESS' - WHEN 'VERIFICATION_FAILED' THEN 'STATUS_CHANGED_TO_VERIFICATION_FAILED' - ELSE NULL - END - INTO v_status_change; - - -- Only insert if we determined a valid status change enum - IF v_status_change IS NOT NULL THEN - INSERT INTO "Notification" ("userId", "type", "aboutServiceId", "aboutServiceVerificationStatusChange") - SELECT np."userId", 'SERVICE_VERIFICATION_STATUS_CHANGE', NEW.id, v_status_change - FROM "_onVerificationChangeForServices" oes - JOIN "NotificationPreferences" np ON oes."A" = np.id -- A -> NotificationPreferences.id - WHERE oes."B" = NEW.id; -- B -> Service.id - END IF; - END IF; - - RETURN NULL; -- Return NULL for AFTER trigger -END; -$$ LANGUAGE plpgsql; - --- Trigger for Service verificationStatus updates -DROP TRIGGER IF EXISTS service_verification_status_change_notifications_trigger ON "Service"; -CREATE TRIGGER service_verification_status_change_notifications_trigger - AFTER UPDATE ON "Service" - FOR EACH ROW - EXECUTE FUNCTION trigger_service_verification_status_change_notifications(); \ No newline at end of file diff --git a/web/prisma/triggers/10_notifications_user_status_change.sql b/web/prisma/triggers/10_notifications_user_status_change.sql deleted file mode 100644 index d968880..0000000 --- a/web/prisma/triggers/10_notifications_user_status_change.sql +++ /dev/null @@ -1,62 +0,0 @@ -CREATE OR REPLACE FUNCTION trigger_user_status_change_notifications() -RETURNS TRIGGER AS $$ -DECLARE - status_change "AccountStatusChange"; -BEGIN - -- Check for admin status change - IF OLD.admin IS DISTINCT FROM NEW.admin THEN - IF NEW.admin = true THEN - status_change := 'ADMIN_TRUE'; - ELSE - status_change := 'ADMIN_FALSE'; - END IF; - INSERT INTO "Notification" ("userId", "type", "aboutAccountStatusChange") - VALUES (NEW.id, 'ACCOUNT_STATUS_CHANGE', status_change); - END IF; - - -- Check for verified status change - IF OLD.verified IS DISTINCT FROM NEW.verified THEN - IF NEW.verified = true THEN - status_change := 'VERIFIED_TRUE'; - ELSE - status_change := 'VERIFIED_FALSE'; - END IF; - INSERT INTO "Notification" ("userId", "type", "aboutAccountStatusChange") - VALUES (NEW.id, 'ACCOUNT_STATUS_CHANGE', status_change); - END IF; - - -- Check for verifier status change - IF OLD.verifier IS DISTINCT FROM NEW.verifier THEN - IF NEW.verifier = true THEN - status_change := 'VERIFIER_TRUE'; - ELSE - status_change := 'VERIFIER_FALSE'; - END IF; - INSERT INTO "Notification" ("userId", "type", "aboutAccountStatusChange") - VALUES (NEW.id, 'ACCOUNT_STATUS_CHANGE', status_change); - END IF; - - -- Check for spammer status change - IF OLD.spammer IS DISTINCT FROM NEW.spammer THEN - IF NEW.spammer = true THEN - status_change := 'SPAMMER_TRUE'; - ELSE - status_change := 'SPAMMER_FALSE'; - END IF; - INSERT INTO "Notification" ("userId", "type", "aboutAccountStatusChange") - VALUES (NEW.id, 'ACCOUNT_STATUS_CHANGE', status_change); - END IF; - - -- Return NULL for AFTER triggers as the return value is ignored. - RETURN NULL; -END; -$$ LANGUAGE plpgsql; - --- Drop the trigger if it exists to ensure a clean setup -DROP TRIGGER IF EXISTS user_status_change_notifications_trigger ON "User"; - --- Create the trigger to fire after updates on specific status columns -CREATE TRIGGER user_status_change_notifications_trigger - AFTER UPDATE OF admin, verified, verifier, spammer ON "User" - FOR EACH ROW - EXECUTE FUNCTION trigger_user_status_change_notifications(); diff --git a/web/public/favicon-dev.svg b/web/public/favicon-dev.svg deleted file mode 100644 index 45ecc30..0000000 --- a/web/public/favicon-dev.svg +++ /dev/null @@ -1,5 +0,0 @@ - - KYCnot.me logo - - diff --git a/web/public/favicon-lightmode.svg b/web/public/favicon-lightmode.svg deleted file mode 100644 index 3adafde..0000000 --- a/web/public/favicon-lightmode.svg +++ /dev/null @@ -1,5 +0,0 @@ - - KYCnot.me logo - - \ No newline at end of file diff --git a/web/public/favicon-stage.svg b/web/public/favicon-stage.svg deleted file mode 100644 index cb164b1..0000000 --- a/web/public/favicon-stage.svg +++ /dev/null @@ -1,12 +0,0 @@ - - KYCnot.me logo - - - \ No newline at end of file diff --git a/web/public/favicon.svg b/web/public/favicon.svg deleted file mode 100644 index 5972705..0000000 --- a/web/public/favicon.svg +++ /dev/null @@ -1,5 +0,0 @@ - - KYCnot.me logo - - \ No newline at end of file diff --git a/web/scripts/faker.ts b/web/scripts/faker.ts deleted file mode 100755 index 19803c0..0000000 --- a/web/scripts/faker.ts +++ /dev/null @@ -1,1333 +0,0 @@ -import crypto from 'crypto' - -import { faker } from '@faker-js/faker' -import { - AttributeCategory, - AttributeType, - CommentStatus, - Currency, - PrismaClient, - ServiceSuggestionStatus, - ServiceSuggestionType, - VerificationStatus, - type Prisma, - EventType, - type User, - ServiceUserRole, -} from '@prisma/client' -import { uniqBy } from 'lodash-es' -import { generateUsername } from 'unique-username-generator' - -import { kycLevels } from '../src/constants/kycLevels' -import { undefinedIfEmpty } from '../src/lib/arrays' -import { transformCase } from '../src/lib/strings' - -// Exit if not in development mode -if (process.env.NODE_ENV === 'production') { - console.error("This script can't run in production mode") - process.exit(1) -} - -/** Duplicate of parseIntWithFallback in src/lib/numbers.ts */ -function parseIntWithFallback(value: unknown, fallback: F = null as F) { - const parsed = Number(value) - if (!Number.isInteger(parsed)) return fallback - return parsed -} - -/** Duplicate of hashUserSecretToken in src/lib/userSecretToken.ts */ -function hashUserSecretToken(token: string): string { - return crypto.createHash('sha512').update(token).digest('hex') -} - -/** Duplicate of generateUserSecretToken in src/lib/userSecretToken.ts */ -export function generateUserSecretToken() { - const LOWERCASE_VOWEL_CHARACTERS = ['a', 'e', 'i', 'o', 'u'] as const - return [ - faker.helpers.arrayElement(LOWERCASE_VOWEL_CHARACTERS), - faker.string.alpha({ length: 1, casing: 'lower', exclude: LOWERCASE_VOWEL_CHARACTERS }), - faker.helpers.arrayElement(LOWERCASE_VOWEL_CHARACTERS), - faker.string.alpha({ length: 1, casing: 'lower', exclude: LOWERCASE_VOWEL_CHARACTERS }), - - faker.helpers.arrayElement(LOWERCASE_VOWEL_CHARACTERS), - faker.string.alpha({ length: 1, casing: 'lower', exclude: LOWERCASE_VOWEL_CHARACTERS }), - faker.helpers.arrayElement(LOWERCASE_VOWEL_CHARACTERS), - faker.string.alpha({ length: 1, casing: 'lower', exclude: LOWERCASE_VOWEL_CHARACTERS }), - - faker.helpers.arrayElement(LOWERCASE_VOWEL_CHARACTERS), - faker.string.alpha({ length: 1, casing: 'lower', exclude: LOWERCASE_VOWEL_CHARACTERS }), - faker.helpers.arrayElement(LOWERCASE_VOWEL_CHARACTERS), - faker.string.alpha({ length: 1, casing: 'lower', exclude: LOWERCASE_VOWEL_CHARACTERS }), - - faker.helpers.arrayElement(LOWERCASE_VOWEL_CHARACTERS), - faker.string.alpha({ length: 1, casing: 'lower', exclude: LOWERCASE_VOWEL_CHARACTERS }), - faker.helpers.arrayElement(LOWERCASE_VOWEL_CHARACTERS), - faker.string.alpha({ length: 1, casing: 'lower', exclude: LOWERCASE_VOWEL_CHARACTERS }), - - faker.string.numeric(1), - faker.string.numeric(1), - faker.string.numeric(1), - faker.string.numeric(1), - ].join('') -} - -/** Duplicate of createAccount in src/lib/accountCreate.ts */ -async function createAccount(preGeneratedToken?: string) { - const token = preGeneratedToken ?? generateUserSecretToken() - const verifiedLink = faker.helpers.maybe(() => faker.internet.url(), { probability: 0.5 }) - - const user = await prisma.user.create({ - data: { - name: `${generateUsername('_')}_${Math.floor(Math.random() * 10000).toString()}`, - secretTokenHash: hashUserSecretToken(token), - notificationPreferences: { create: {} }, - verifiedLink, - verified: !!verifiedLink, - admin: faker.datatype.boolean({ probability: 0.1 }), - verifier: faker.datatype.boolean({ probability: 0.1 }), - }, - include: { - serviceAffiliations: true, - }, - }) - - return { token, user } -} - -// Parse command line arguments -const args = process.argv.slice(2) -const shouldCleanup = args.includes('--cleanup') || args.includes('-c') -const onlyCleanup = args.includes('--only-cleanup') || args.includes('-oc') - -// Parse number of services from --services or -s flag -const servicesArg = args.find((arg) => arg.startsWith('--services=') || arg.startsWith('-s=')) -const numServices = parseIntWithFallback(servicesArg?.split('=')[1], 100) // Default to 100 if not specified - -if (isNaN(numServices) || numServices < 1) { - console.error('❌ Invalid number of services specified. Must be a positive number.') - process.exit(1) -} - -const prisma = new PrismaClient() - -const generateFakeAttribute = () => { - const title = transformCase(faker.lorem.words({ min: 1, max: 4 }), 'sentence') - const slug = `${faker.helpers.slugify(title).toLowerCase()}-${faker.string.numeric({ length: 2 })}` - const type = faker.helpers.arrayElement(Object.values(AttributeType)) - const category = faker.helpers.arrayElement(Object.values(AttributeCategory)) - const attributePointsByType = { - [AttributeType.GOOD]: { min: 0, max: 10 }, - [AttributeType.BAD]: { min: -10, max: 0 }, - [AttributeType.WARNING]: { min: -5, max: 0 }, - [AttributeType.INFO]: { min: 0, max: 0 }, - } as const satisfies Record[0]> - const attributePointsByTypeWrongCategory = { - [AttributeType.GOOD]: { min: 0, max: 1 }, - [AttributeType.BAD]: { min: -1, max: 0 }, - [AttributeType.WARNING]: { min: -1, max: 0 }, - [AttributeType.INFO]: { min: 0, max: 0 }, - } as const satisfies Record[0]> - - return { - title, - slug, - description: faker.lorem.sentences({ min: 1, max: 3 }), - privacyPoints: - category === 'PRIVACY' - ? faker.number.int(attributePointsByType[type]) - : faker.number.int(attributePointsByTypeWrongCategory[type]), - trustPoints: - category === 'TRUST' - ? faker.number.int(attributePointsByType[type]) - : faker.number.int(attributePointsByTypeWrongCategory[type]), - category, - type, - } as const satisfies Prisma.AttributeCreateInput -} - -const categoriesToCreate = [ - { - name: 'Exchange', - slug: 'exchange', - icon: 'ri:arrow-left-right-fill', - }, - { - name: 'VPN', - slug: 'vpn', - icon: 'ri:door-lock-fill', - }, - { - name: 'Email', - slug: 'email', - icon: 'ri:mail-fill', - }, - { - name: 'Hosting', - slug: 'hosting', - icon: 'ri:server-fill', - }, - { - name: 'VPS', - slug: 'vps', - icon: 'ri:function-add-fill', - }, - { - name: 'Gift Cards', - slug: 'gift-cards', - icon: 'ri:gift-line', - }, - { - name: 'Goods', - slug: 'goods', - icon: 'ri:shopping-basket-fill', - }, - { - name: 'Travel', - slug: 'travel', - icon: 'ri:plane-fill', - }, - { - name: 'SMS', - slug: 'sms', - icon: 'ri:message-2-fill', - }, - { - name: 'Store', - slug: 'store', - icon: 'ri:store-2-line', - }, - { - name: 'Tool', - slug: 'tool', - icon: 'ri:tools-fill', - }, - { - name: 'Market', - slug: 'market', - icon: 'ri:price-tag-3-line', - }, - { - name: 'Aggregator', - slug: 'aggregator', - icon: 'ri:list-ordered', - }, - { - name: 'AI', - slug: 'ai', - icon: 'ri:ai-generate-2', - }, - { - name: 'CEX', - slug: 'cex', - icon: 'ri:rotate-lock-fill', - }, - { - name: 'DEX', - slug: 'dex', - icon: 'ri:fediverse-line', - }, -] as const satisfies Prisma.CategoryCreateInput[] - -const serviceNames = [ - 'MajesticBank', - 'eXch', - 'Mullvad VPN', - 'iVPN', - 'Coinbase', - 'Binance', - 'Kraken', - 'Coinbase Pro', - 'Bitfinex', - 'KuCoin', - 'Bitstamp', - 'Gemini', - 'Bitpanda', - 'Bitpanda Pro', - 'MyNymBox', - 'ProtonVPN', - 'Proxystore', - 'WizardSwap', - 'OrangeFren', - 'Trocador', - 'Bisq', - 'Sms4Sats', - 'Vexl', - 'Haveno', - 'BasicSwap Beta', - 'TheLongServiceName Service', - 'TheVeryVeryVeryLongServiceName Service', - 'The Very Very Very Long Service Name Service', - 'The %4W3*ird _?sym[bol$] $#ervice', - 'The Service', - 'Random', - 'Anonymous', - 'Atomic Technologies', - 'France', - '8a9a j9a0', -] - -const serviceDescriptions = [ - "Buy and sell bitcoin for fiat (or other cryptocurrencies) privately and securely using Bisq's peer-to-peer network and open-source desktop software. No registration required.", - 'Bitcoin -> Monero atomic swaps, securely and in a decentralized manner using a state-of-the-art cryptographic protocol and open-source desktop software.', - 'P2P exchange bitcoin for national currencies. Robosats simplifies the peer-to-peer user experience.', - 'Anonymous exchange: Exchange Bitcoin to Monero and vice versa.', - 'Private web Hosting, KVM VPS, Dedicated Servers, Domain Names and VPN.', - 'SMS verification numbers online, pay using the Lightning Network. Cheap, easy, fast and anonymous.', - 'Hosting solutions, servers, domain registrations and dns parking. We do not require any personal information. Pay with Bitcoin and Monero.', - 'Send and receive sms messages via an XMPP client. You can also make and receive phone calls.', - 'VPN with unlimited bandwidth, dedicated servers without hard drives, no logging VPN service that accepts Monero.', - 'Privacy-first automated crytpocurrency swaps without registration.', - 'High-speed VPN available with multiple protocols, with strict no-logs policy and based in Switzerland.', - 'No logs, fully anonymous VPN. Resist Online Surveillance.', - 'Boltz is a non-custodial Bitcoin bridge built to swap between different Bitcoin layers like the Liquid and Lightning Network. Boltz Swaps are non-custodial, which means users can always rest assured to be in full control of their bitcoin throughout the entire flow of a swap.', - 'Iceland-based freedom of speech web hosting provider offering high-quality and secure web hosting solutions to its customers worldwide, with an award-winning customer support team.', - 'Privacy-friendly VPN with strong cryptography, no logs, anonymous payment methods, and Tor and I2P access. They support OpenVPN and WireGuard.', - 'Peach is a mobile application that connects Bitcoin Buyers & Sellers together. Buy or sell bitcoin peer-to-peer, anywhere, at anytime, with the payment method of your choice.', - 'Use GPT4 (and more) without accounts, subscriptions or credit cards. The interface runs on a pay-per-query model via Lightning.', - 'Xchange.me offers a cryptocurrency exchange service that allows you to exchange cryptocurrency through a fast automated process. No registration process or lengthy verification is needed.', - 'Exchange more than 1200+ coins on all available networks, quickly and easily.', - 'Swap between coins with fast and easy user experience, no sign-up required.', - 'Instant swap service, with no mandatory account registration. Fixed and floating rates.', - 'P2P marketplace that accepts Monero. It is similar to MoneroMarket and Facebook Marketplace. Messenger for buyer/seller is included with PGP encryption.', -] - -const tosReviewExamples: PrismaJson.TosReview[] = [ - { - kycLevel: 1, - summary: '**Non-KYC exchange** with strong privacy features, but registered in Belize.', - complexity: 'medium', - contentHash: faker.string.uuid(), - highlights: [ - { - title: 'No KYC Required', - content: 'No KYC or Source of Funds verification required for transactions.', - rating: 'positive', - }, - { - title: 'Privacy Protection', - content: 'No metadata collection (IP addresses, browser information, etc.).', - rating: 'positive', - }, - { - title: 'Tor Support', - content: 'Offers .onion address for enhanced privacy through Tor network.', - rating: 'positive', - }, - { - title: 'Transparency', - content: 'Provides proof of reserves on request, enhancing trust.', - rating: 'positive', - }, - { - title: 'Privacy Coins', - content: 'Supports privacy-focused cryptocurrencies like Monero.', - rating: 'positive', - }, - { - title: 'Jurisdiction Risk', - content: 'Registered in Belize which may have lax regulatory oversight.', - rating: 'negative', - }, - { - title: 'Transaction Risk', - content: 'Mixed pool transactions may lead to frozen funds on major exchanges.', - rating: 'negative', - }, - { - title: 'Privacy Trade-off', - content: 'Aggregated pool reduces risk of frozen funds but compromises privacy.', - rating: 'neutral', - }, - { - title: 'Mobile Security', - content: 'Mobile wallets recommended only if available on F-Droid (reproducible builds).', - rating: 'neutral', - }, - { - title: 'Messaging Security', - content: 'Avoid Telegram bots for exchanges due to lack of end-to-end encryption.', - rating: 'negative', - }, - ], - }, - { - kycLevel: 2, - summary: - 'MajesticBank offers privacy-conscious crypto exchange services, with no mandatory registration, no logging, encryption, and optional JavaScript usage. Ensures anonymity and sovereignty through privacy-oriented features like Tor access and log-free practices.', - complexity: 'low', - contentHash: faker.string.uuid(), - highlights: [ - { - title: 'Anonymous Exchange', - content: 'Registration is not required, supporting anonymous exchanges.', - rating: 'positive', - }, - { - title: 'Limited Data Retention', - content: 'No logs are kept, and exchange data is deleted upon request or after two weeks.', - rating: 'positive', - }, - { - title: 'Strong Encryption', - content: 'Military-grade encryption ensures user data security.', - rating: 'positive', - }, - { - title: 'Optional JavaScript', - content: 'Optional JavaScript enhances security for privacy-conscious users.', - rating: 'positive', - }, - { - title: 'Hidden Transactions', - content: 'Default hidden transaction ID prioritizes user privacy.', - rating: 'positive', - }, - { - title: 'Clean Coin History', - content: 'Clean coin history ensures safe usability of exchanged cryptocurrencies.', - rating: 'positive', - }, - { - title: 'Tor Support', - content: 'Recommended Tor v3 hidden service for secure access supports self-sovereignty.', - rating: 'positive', - }, - ], - }, - { - kycLevel: 3, - summary: - '**SideShift.ai blocks users from certain countries** including the US, North Korea, and others. Restrictions on SideShift Token (XAI) apply particularly for US residents, limiting functionality and access.', - complexity: 'low', - contentHash: faker.string.uuid(), - highlights: [ - { - title: 'No KYC Mentioned', - content: - 'SideShift.ai excludes KYC requirements in the text—potential benefit for anonymity in non-blocked jurisdictions.', - rating: 'positive', - }, - { - title: 'Privacy-First Design', - content: - 'Its absence of direct data collection mentions could imply privacy-first design for eligible users.', - rating: 'positive', - }, - ], - }, - { - kycLevel: 4, - summary: - '**SideShift.ai blocks users from certain countries** including the US, North Korea, and others. Restrictions on SideShift Token (XAI) apply particularly for US residents, limiting functionality and access.', - complexity: 'low', - contentHash: faker.string.uuid(), - highlights: [ - { - title: 'No KYC Mentioned', - content: - 'SideShift.ai excludes KYC requirements in the text—potential benefit for anonymity in non-blocked jurisdictions.', - rating: 'positive', - }, - { - title: 'Privacy-First Design', - content: - 'Its absence of direct data collection mentions could imply privacy-first design for eligible users.', - rating: 'positive', - }, - ], - }, - { - kycLevel: 0, - summary: - '**Kyun! Terms of Service** emphasize data collection with potential privacy risks. Key clauses suggest user tracking, require KYC for services, and describe limited protections for anonymity.', - complexity: 'medium', - contentHash: faker.string.uuid(), - highlights: [ - { - title: 'Tor Integration', - content: 'Integration with Tor for enhanced anonymity.', - rating: 'positive', - }, - { - title: 'Transparency Measures', - content: 'A published canary file and PGP keys provide additional transparency.', - rating: 'positive', - }, - { - title: 'Data Control', - content: 'Privacy Policy indicates a degree of user control over personal data requests.', - rating: 'neutral', - }, - ], - }, -] - -// User sentiment examples for AI-generated summaries -const generateFakeUserSentiment = () => { - const sentiments = ['positive', 'neutral', 'negative'] as const - const sentiment = faker.helpers.arrayElement(sentiments) - - // Generate what users like based on sentiment - const likeCount = - sentiment === 'positive' - ? faker.number.int({ min: 3, max: 6 }) - : sentiment === 'neutral' - ? faker.number.int({ min: 1, max: 4 }) - : faker.number.int({ min: 0, max: 2 }) - - // Generate what users dislike based on sentiment - const dislikeCount = - sentiment === 'negative' - ? faker.number.int({ min: 3, max: 6 }) - : sentiment === 'neutral' - ? faker.number.int({ min: 1, max: 4 }) - : faker.number.int({ min: 0, max: 2 }) - - const whatUsersLike = Array.from({ length: likeCount }, () => - faker.helpers.arrayElement([ - 'Fast transaction times', - 'No KYC required', - 'Excellent support team', - 'Low fees', - 'Multiple currency options', - 'Easy to use interface', - 'Clear documentation', - 'Tor support', - 'Strong privacy policies', - 'No IP logging', - 'Quick verification process', - 'Responsive website', - 'Mobile-friendly design', - 'Reliable uptime', - 'Transparent fee structure', - ]) - ) - - const whatUsersDislike = Array.from({ length: dislikeCount }, () => - faker.helpers.arrayElement([ - 'Slow transaction times', - 'High fees', - 'Poor customer support', - 'Confusing interface', - 'Limited currency options', - 'Website downtime', - 'Hidden fees', - 'No mobile support', - 'Lack of transparency', - 'Limited payment methods', - 'Bugs in the platform', - 'Restrictive limits', - 'Delayed withdrawals', - 'Verification issues', - 'Complicated signup process', - ]) - ) - - // Create summary based on sentiment - let summary = '' - if (sentiment === 'positive') { - summary = faker.helpers.arrayElement([ - `Users overwhelmingly praise this service for its ${faker.helpers.arrayElements(whatUsersLike, 2).join(' and ')}. Many highlight the ${faker.helpers.arrayElement(whatUsersLike)} as a standout feature.`, - `Based on multiple user reviews, this service receives high marks for ${faker.helpers.arrayElements(whatUsersLike, 2).join(' and ')}. Most users report positive experiences with minimal issues.`, - `Community feedback indicates strong satisfaction with this service, particularly regarding ${faker.helpers.arrayElements(whatUsersLike, 2).join(' and ')}.`, - ]) - } else if (sentiment === 'neutral') { - summary = faker.helpers.arrayElement([ - `User sentiment is mixed. While many appreciate the ${faker.helpers.arrayElement(whatUsersLike)}, common complaints include ${faker.helpers.arrayElement(whatUsersDislike)}.`, - `Community feedback shows balanced opinions. Users like the ${faker.helpers.arrayElement(whatUsersLike)} but have concerns about ${faker.helpers.arrayElement(whatUsersDislike)}.`, - `Analysis of reviews indicates neither overwhelmingly positive nor negative sentiment. Users value ${faker.helpers.arrayElement(whatUsersLike)} but criticize ${faker.helpers.arrayElement(whatUsersDislike)}.`, - ]) - } else { - summary = faker.helpers.arrayElement([ - `User reviews highlight significant concerns with this service, primarily regarding ${faker.helpers.arrayElements(whatUsersDislike, 2).join(' and ')}. Few users mention positive aspects.`, - `Community feedback is predominantly negative, with recurring complaints about ${faker.helpers.arrayElements(whatUsersDislike, 2).join(' and ')}.`, - `Analysis of user comments reveals widespread dissatisfaction, especially concerning ${faker.helpers.arrayElements(whatUsersDislike, 2).join(' and ')}.`, - ]) - } - - return { - summary, - sentiment, - whatUsersLike: [...new Set(whatUsersLike)], - whatUsersDislike: [...new Set(whatUsersDislike)], - } -} - -const eventTitles = [ - 'Service maintenance scheduled', - 'Server outage reported', - 'New feature launched', - 'Security update released', - 'Price changes announced', - 'Service temporarily unavailable', - 'Verification process changed', - 'High traffic warning', - 'API changes coming soon', - 'Database maintenance', - 'KYC policy updated', - 'Privacy policy update', - 'New cryptocurrencies added', - 'Lower fees promotion', - 'Important security notification', - 'Partial service disruption', - 'Full service restored', - 'Address format changed', - 'Exchange rate issues fixed', - 'Holiday operating hours', -] - -const generateFakeEvent = (serviceId: number) => { - const type = faker.helpers.arrayElement(Object.values(EventType)) - const visible = faker.datatype.boolean(0.9) // 90% chance of being visible - const startedAt = faker.date.between({ - from: new Date(Date.now() - 90 * 24 * 60 * 60 * 1000), // 90 days ago - to: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30 days in future - }) - const endedAt = faker.helpers.arrayElement([ - // Option 1: Future date (1-14 days after start) - faker.date.between({ - from: startedAt, - to: new Date(startedAt.getTime() + faker.number.int({ min: 1, max: 14 }) * 24 * 60 * 60 * 1000), - }), - // Option 2: null (ongoing event) - null, - // Option 3: Same date as startedAt (one-time event) - new Date(startedAt), - ]) - - const title = faker.helpers.arrayElement(eventTitles) - - return { - title, - content: faker.lorem.sentence({ min: 1, max: 10 }), - source: faker.helpers.maybe(() => faker.internet.url(), { probability: 0.7 }), - type, - visible, - startedAt, - endedAt, - service: { connect: { id: serviceId } }, - } as const satisfies Prisma.EventCreateInput -} - -const generateFakeService = (users: User[]) => { - const status = faker.helpers.arrayElement(Object.values(VerificationStatus)) - const name = faker.helpers.arrayElement(serviceNames) - const slug = `${faker.helpers.slugify(name).toLowerCase()}-${faker.string.alphanumeric({ length: 6, casing: 'lower' })}` - - return { - name, - slug, - description: faker.helpers.arrayElement(serviceDescriptions), - kycLevel: faker.helpers.arrayElement(kycLevels.map((level) => level.value)), - overallScore: 0, - privacyScore: 0, - trustScore: 0, - verificationStatus: status, - verificationSummary: - status === 'VERIFICATION_SUCCESS' || status === 'VERIFICATION_FAILED' ? faker.lorem.paragraph() : null, - verificationRequests: { - create: uniqBy( - Array.from({ length: faker.number.int({ min: 0, max: 10 }) }, () => ({ - userId: faker.helpers.arrayElement(users).id, - })), - 'userId' - ), - }, - verificationProofMd: - status === 'VERIFICATION_SUCCESS' || status === 'VERIFICATION_FAILED' ? faker.lorem.paragraphs() : null, - referral: `?ref=${faker.string.alphanumeric(6)}`, - acceptedCurrencies: faker.helpers.arrayElements(Object.values(Currency), { min: 1, max: 5 }), - serviceUrls: Array.from({ length: faker.number.int({ min: 1, max: 3 }) }, () => faker.internet.url()), - tosUrls: Array.from({ length: faker.number.int({ min: 0, max: 2 }) }, () => faker.internet.url()), - onionUrls: Array.from( - { length: faker.number.int({ min: 0, max: 2 }) }, - () => `http://${faker.string.alphanumeric({ length: 56, casing: 'lower' })}.onion` - ), - i2pUrls: Array.from( - { length: faker.number.int({ min: 0, max: 2 }) }, - () => `http://${faker.string.alphanumeric({ length: 52, casing: 'lower' })}.b32.i2p` - ), - imageUrl: `https://ui-avatars.com/api/?name=${encodeURIComponent(name)}&background=random&format=svg`, - listedAt: faker.date.past(), - verifiedAt: status === VerificationStatus.VERIFICATION_SUCCESS ? faker.date.past() : null, - tosReview: faker.helpers.arrayElement(tosReviewExamples), - tosReviewAt: faker.date.past(), - userSentiment: Math.random() > 0.2 ? generateFakeUserSentiment() : undefined, - userSentimentAt: faker.date.recent(), - } as const satisfies Prisma.ServiceCreateInput -} - -const commentList = [ - 'This service is amazing!', - "I've had a great experience with this service.", - 'Would recommend to everyone.', - 'Not the best service, but it works.', - "I've had some issues with this service.", - 'Avoid this service at all costs.', - "It's been over 12 hours. Page just says 'Something went wrong. Try to generate new circuit or get back to homepage' entering exchange ID in track says it cannot be found. out $500", - 'Wow man so slow exchange im starting to think i just got scammed', - "takes very long to swap. Waiting for almost 2h using 'Priority' transaction but still waiting. Reasonable service but very slow. Thank you", - 'If you are in a hurry, i advise against using this site. It took 2h to process...', - 'I had to wait 2 hours for my transaction to be processed. I was very disappointed.', - 'Good service, low fees, would use again.', - 'exchange on majesticbank takes 30 minutes after confirmations and slower compared to other eXchange services but trusted too.', - 'scam', - 'test', - 'good morning everyone', - 'shitty service, bad support team, avoid', - 'hey admin, please could you check the service URLs? they seem to have changed', - 'they are now accepting lightning payments, which is great!', - 'I have been using this service for a while now and I must say that it is one of the best I have used. The customer support is excellent and the service is very reliable. I would highly recommend this service to anyone looking for a reliable and trustworthy service.', - 'last time i used this was a long ago, but they were good', - - // Positive, high-quality reviews - 'This service is amazing! The transaction was smooth and support team was very helpful throughout.', - "I've had a great experience with this service. Fast processing times and reasonable fees.", - 'Would definitely recommend to everyone - saved me a lot in fees compared to competitors.', - 'Their new Lightning Network integration is fantastic. Makes transactions so much faster.', - "One of the most reliable services I've used in 5+ years of crypto trading.", - - // Balanced/Mixed reviews - 'Not the best service, but it works well enough for basic transactions.', - "I've had some issues but support helped resolve them quickly.", - 'Exchange rates could be better but service is reliable.', - 'A bit slow sometimes but very secure and trustworthy.', - 'Interface needs work but core functionality is solid.', - - // Negative but legitimate reviews - 'Avoid this service if you need fast transactions. Too slow.', - 'Support took 3 days to respond to my ticket. Not acceptable.', - 'Fees are higher than advertised. Be careful.', - 'Site was down for maintenance during peak hours.', - 'Mobile experience is terrible, desktop only works properly.', - - // Spam/Low quality - 'scam scam scam!!!11', - 'WORST SERVICE EVER!!!!!', - 'test test', - 'first', - 'nice', - 'good', - 'hi admin', - 'check dm', - 'lol', - '+1', - - // Potential scam/fraud - 'DOUBLE YOUR BITCOIN - Send 0.1 BTC to address xyz123...', - 'FREE CRYPTO GIVEAWAY at totally-legit-site.com', - 'DM me for special rates', - 'WhatsApp +123456789 for instant exchange', - 'Guaranteed 100% returns daily!!!', - - // Time-wasting/Trolling - 'Does this work with Dogecoin? Asking for my cat.', - 'Instructions unclear, bought a lambo instead', - 'When moon? When lambo?', - 'This is the way', - 'HODL!!!', - - // Long rambling reviews - "I have been using this service for approximately 2.4 years and I must say that while initially I was skeptical due to the interface design choices particularly the color scheme which reminds me of my grandmother's curtains from the 1970s but that's besides the point because ultimately what matters is functionality and in that regard I can definitively state that based on my extensive experience with various competing services including but not limited to...", - - // Technical issues/bugs - "It's been over 12 hours. Page just says 'Something went wrong. Try to generate new circuit or get back to homepage' entering exchange ID in track says it cannot be found. out $500", - 'Error 404 on confirmation page', - 'API keeps timing out', - 'Cloudflare is blocking access', - 'KYC verification stuck at 99%', - - // Legitimate but poorly written - 'gud service fast n cheap', - 'works ok i guess', - 'better then others maybe', - 'ya its fine whatever', - 'does job', - - // Support questions/issues - 'hey admin, please check support ticket #12345', - 'when will site maintenance end???', - 'need help with transaction', - 'support not responding!!!', - 'how to cancel order??', - - // Requires admin attention - 'The correct name of this service is "MoneroSMS", not "monero sms" (no space)', - 'Since they recommend using a swap service. I don\'t think it can be labled as "support monero".', - 'new tor address is \nhttp://4kanxsg3sbveimuoqgxhaj3clrj2aw7swow5fpc6odqmtclww3ukcqqd.onion/', - - // Feature requests/suggestions - 'Please add support for Monero', - 'Dark mode would be nice', - 'Mobile app when?', - 'Can we get lower fees for high volume?', - 'Add more payment methods please', - - // Time-related complaints - 'Wow man so slow exchange im starting to think i just got scammed', - 'takes very long to swap. Waiting for almost 2h using Priority transaction', - 'If you are in a hurry, avoid - took 2h to process...', - '30+ minutes after confirmation still waiting', - 'Stuck pending for 3 hours now', - - // Competitor mentions/comparisons - 'Much slower than competitor X', - 'Fees higher than service Y', - 'Other exchanges are faster', - 'Better rates on Z exchange', - 'Moving to competitor service', - "To activate your account, you can either deposit $15 to your balance or enter your referral code if you have one. \n(If you'd like to pay in a cryptocurrency other than Bitcoin, currently we recommend using a service like simpleswap.io, morphtoken.com, changenow.io, or godex.io. Manual payment via Bitcoin Cash is also available if you contact support.)", - - // Random/nonsensical - 'potato', - 'asdfghjkl', - 'testing testing 123', - '........................', - '🚀🚀🚀🚀🚀', - - // Old reviews - 'Used this back in 2019, was good then', - 'Last time I checked was months ago', - 'Things have changed since I last used it', - 'Service quality has declined since early days', - 'Not as good as it used to be', -] - -const commentReplyList = [ - // Replies to comments - 'yup', - 'noope', - 'yeah', - 'yes', - 'maybe', - 'thanks', - 'thank you', - 'thx', - 'thx for the help', - 'right', - 'same here', - 'same issue man', - 'same', - 'same problem', - 'same problem here', - 'same problem man', - 'same problem here man', - 'same experience', - 'same experience here', - 'same experience here man', - 'same experience man', - 'same experience here man', -] - -const generateFakeComment = (userId: number, serviceId: number, parentId?: number) => - ({ - upvotes: faker.number.int({ min: 0, max: 100 }), - status: - Math.random() > 0.1 - ? faker.helpers.arrayElement([CommentStatus.APPROVED, CommentStatus.REJECTED, CommentStatus.VERIFIED]) - : CommentStatus.PENDING, - suspicious: Math.random() > 0.2 ? false : faker.datatype.boolean(), - communityNote: Math.random() > 0.2 ? '' : faker.lorem.paragraph(), - internalNote: Math.random() > 0.2 ? '' : faker.lorem.paragraph(), - privateContext: Math.random() > 0.2 ? '' : faker.lorem.paragraph(), - content: - parentId && Math.random() > 0.3 - ? faker.helpers.arrayElement(commentReplyList) - : faker.helpers.arrayElement(commentList), - rating: parentId ? null : Math.random() < 0.33 ? faker.number.int({ min: 1, max: 5 }) : null, - ratingActive: false as boolean, - authorId: userId, - serviceId, - parentId, - }) satisfies Prisma.CommentCreateManyInput - -const generateFakeServiceContactMethod = (serviceId: number) => { - const types = [ - { - label: 'Email', - value: `mailto:${faker.internet.email()}`, - iconId: 'ri:mail-line', - info: faker.lorem.sentence(), - }, - { - label: 'Phone', - value: `tel:${faker.phone.number({ style: 'international' })}`, - iconId: 'ri:phone-line', - info: faker.lorem.sentence(), - }, - { - label: 'WhatsApp', - value: `https://wa.me/${faker.phone.number({ style: 'international' })}`, - iconId: 'ri:whatsapp-line', - info: faker.lorem.sentence(), - }, - { - label: 'Telegram', - value: `https://t.me/${faker.internet.username()}`, - iconId: 'ri:telegram-line', - info: faker.lorem.sentence(), - }, - { - label: 'Website', - value: faker.internet.url(), - iconId: 'ri:global-line', - info: faker.lorem.sentence(), - }, - { - label: 'LinkedIn', - value: `https://www.linkedin.com/company/${faker.helpers.slugify(faker.company.name())}`, - iconId: 'ri:linkedin-box-line', - info: faker.lorem.sentence(), - }, - ] as const satisfies Partial[] - - return { - services: { connect: { id: serviceId } }, - ...faker.helpers.arrayElement(types), - } as const satisfies Prisma.ServiceContactMethodCreateInput -} - -const specialUsersData = { - admin: { - name: 'admin_dev', - envToken: 'DEV_ADMIN_USER_SECRET_TOKEN', - defaultToken: 'admin', - admin: true, - verifier: true, - verified: true, - verifiedLink: 'https://kycnot.me', - totalKarma: 1001, - link: 'https://kycnot.me', - picture: 'https://comments.kycnot.me/api/users/549f290e-0542-4c18-b437-5b64b35758f0/avatar?size=L', - }, - verifier: { - name: 'verifier_dev', - envToken: 'DEV_VERIFIER_USER_SECRET_TOKEN', - defaultToken: 'verifier', - admin: false, - verifier: true, - verified: true, - verifiedLink: 'https://kycnot.me', - totalKarma: 1001, - link: 'https://kycnot.me', - picture: 'https://comments.kycnot.me/api/users/549f290e-0542-4c18-b437-5b64b35758f0/avatar?size=L', - }, - verified: { - name: 'verified_dev', - envToken: 'DEV_VERIFIED_USER_SECRET_TOKEN', - defaultToken: 'verified', - admin: false, - verifier: false, - verified: true, - verifiedLink: 'https://kycnot.me', - totalKarma: 1001, - }, - normal: { - name: 'normal_dev', - envToken: 'DEV_NORMAL_USER_SECRET_TOKEN', - defaultToken: 'normal', - admin: false, - verifier: false, - verified: false, - }, - spam: { - name: 'spam_dev', - envToken: 'DEV_SPAM_USER_SECRET_TOKEN', - defaultToken: 'spam', - admin: false, - verifier: false, - verified: false, - totalKarma: -100, - spammer: true, - }, -} as const satisfies Record< - string, - Omit & { - envToken: string - defaultToken: string - } -> - -const generateFakeServiceSuggestionMessage = (suggestionId: number, userIds: number[]) => - ({ - content: faker.lorem.paragraph(), - user: { connect: { id: faker.helpers.arrayElement(userIds) } }, - suggestion: { connect: { id: suggestionId } }, - }) satisfies Prisma.ServiceSuggestionMessageCreateInput - -const generateFakeServiceSuggestion = ({ - type, - status = ServiceSuggestionStatus.PENDING, - userId, - serviceId, -}: { - type: ServiceSuggestionType - status?: ServiceSuggestionStatus - userId: number - serviceId: number -}) => - ({ - type, - status, - notes: faker.lorem.paragraph(), - user: { connect: { id: userId } }, - service: { connect: { id: serviceId } }, - }) satisfies Prisma.ServiceSuggestionCreateInput - -const generateFakeInternalNote = (userId: number, addedByUserId?: number) => - ({ - content: faker.lorem.paragraph(), - user: { connect: { id: userId } }, - addedByUser: addedByUserId ? { connect: { id: addedByUserId } } : undefined, - }) satisfies Prisma.InternalUserNoteCreateInput - -async function runFaker() { - await prisma.$transaction( - async (tx) => { - // ---- Clean up existing data if requested ---- - if (shouldCleanup || onlyCleanup) { - console.info('🧹 Cleaning up existing data...') - - try { - await tx.commentVote.deleteMany() - await tx.karmaTransaction.deleteMany() - await tx.comment.deleteMany() - await tx.serviceAttribute.deleteMany() - await tx.serviceContactMethod.deleteMany() - await tx.event.deleteMany() - await tx.verificationStep.deleteMany() - await tx.serviceSuggestionMessage.deleteMany() - await tx.serviceSuggestion.deleteMany() - await tx.serviceVerificationRequest.deleteMany() - await tx.service.deleteMany() - await tx.attribute.deleteMany() - await tx.category.deleteMany() - await tx.internalUserNote.deleteMany() - await tx.user.deleteMany() - console.info('✅ Existing data cleaned up') - } catch (error) { - console.error('❌ Error cleaning up data:', error) - throw error - } - if (onlyCleanup) return - } - - // ---- Get or create categories ---- - const categories = await Promise.all( - categoriesToCreate.map(async (cat) => { - const existing = await tx.category.findUnique({ - where: { name: cat.name }, - }) - if (existing) return existing - - return await tx.category.create({ - data: cat, - }) - }) - ) - - // ---- Create users ---- - const specialUsersUntyped = Object.fromEntries( - await Promise.all( - Object.entries(specialUsersData).map(async ([key, userData]) => { - const secretToken = process.env[userData.envToken] ?? userData.defaultToken - const secretTokenHash = hashUserSecretToken(secretToken) - - const { envToken, defaultToken, ...userCreateData } = userData - const user = await tx.user.create({ - data: { - notificationPreferences: { create: {} }, - ...userCreateData, - secretTokenHash, - }, - }) - - console.info(`✅ Created ${user.name} with secret token "${secretToken}"`) - - return [key, user] as const - }) - ) - ) - - const specialUsers = specialUsersUntyped as { - [K in keyof typeof specialUsersData]: (typeof specialUsersUntyped)[K] - } - - let users = await Promise.all( - Array.from({ length: 10 }, async () => { - const { user } = await createAccount() - return user - }) - ) - - // ---- Create attributes ---- - const attributes = await Promise.all( - Array.from({ length: 16 }, async () => { - return await tx.attribute.create({ - data: generateFakeAttribute(), - }) - }) - ) - - // ---- Create services ---- - const services = await Promise.all( - Array.from({ length: numServices }, async () => { - const serviceData = generateFakeService(users) - const randomCategories = faker.helpers.arrayElements(categories, { min: 1, max: 3 }) - - const service = await tx.service.create({ - data: { - ...serviceData, - categories: { - connect: randomCategories.map((cat) => ({ id: cat.id })), - }, - }, - }) - - // Create contact methods for each service - await Promise.all( - Array.from({ length: faker.number.int({ min: 1, max: 3 }) }, () => - tx.serviceContactMethod.create({ - data: generateFakeServiceContactMethod(service.id), - }) - ) - ) - - // Link random attributes to the service - await Promise.all( - faker.helpers.arrayElements(attributes, { min: 2, max: 5 }).map((attr) => - tx.serviceAttribute.create({ - data: { - serviceId: service.id, - attributeId: attr.id, - }, - }) - ) - ) - - // Create events for the service - await Promise.all( - Array.from({ length: faker.number.int({ min: 0, max: 5 }) }, () => - tx.event.create({ - data: generateFakeEvent(service.id), - }) - ) - ) - - return service - }) - ) - - // ---- Create service user affiliations for the service ---- - await Promise.all( - users - .filter((user) => user.verified) - .map(async (user) => { - const servicesToAddAffiliations = uniqBy( - faker.helpers.arrayElements(services, { - min: 1, - max: 3, - }), - 'id' - ) - - return tx.user.update({ - where: { id: user.id }, - data: { - serviceAffiliations: { - createMany: { - data: servicesToAddAffiliations.map((service) => ({ - role: faker.helpers.arrayElement(Object.values(ServiceUserRole)), - serviceId: service.id, - })), - }, - }, - }, - }) - }) - ) - - users = await tx.user.findMany({ - include: { - serviceAffiliations: true, - }, - }) - - // ---- Create comments and replies ---- - await Promise.all( - services.map(async (service) => { - // Create parent comments - const commentCount = faker.number.int({ min: 1, max: 10 }) - const commentData = Array.from({ length: commentCount }, () => - generateFakeComment(faker.helpers.arrayElement(users).id, service.id) - ) - const indexesToUpdate = users.map((user) => { - return commentData.findIndex((comment) => comment.authorId === user.id && comment.rating !== null) - }) - commentData.forEach((comment, index) => { - if (indexesToUpdate.includes(index)) comment.ratingActive = true - }) - - await tx.comment.createMany({ - data: commentData, - }) - - const comments = await tx.comment.findMany({ - where: { - serviceId: service.id, - parentId: null, - }, - orderBy: { - createdAt: 'desc', - }, - take: commentCount, - }) - - const affiliatedUsers = undefinedIfEmpty( - users.filter((user) => - user.serviceAffiliations.some((affiliation) => affiliation.serviceId === service.id) - ) - ) - - // Create replies to comments - await Promise.all( - comments.map(async (comment) => { - const replyCount = faker.number.int({ min: 0, max: 3 }) - return Promise.all( - Array.from({ length: replyCount }, () => { - const user = faker.helpers.arrayElement( - faker.helpers.maybe(() => affiliatedUsers, { probability: 0.3 }) ?? users - ) - - return tx.comment.create({ - data: generateFakeComment(user.id, service.id, comment.id), - }) - }) - ) - }) - ) - }) - ) - - // ---- Create service suggestions for normal_dev user ---- - // First create 3 CREATE_SERVICE suggestions with their services - for (let i = 0; i < 3; i++) { - const serviceData = generateFakeService(users) - const randomCategories = faker.helpers.arrayElements(categories, { min: 1, max: 3 }) - - const service = await tx.service.create({ - data: { - ...serviceData, - verificationStatus: VerificationStatus.COMMUNITY_CONTRIBUTED, - categories: { - connect: randomCategories.map((cat) => ({ id: cat.id })), - }, - }, - }) - - const serviceSuggestion = await tx.serviceSuggestion.create({ - data: generateFakeServiceSuggestion({ - type: ServiceSuggestionType.CREATE_SERVICE, - userId: specialUsers.normal.id, - serviceId: service.id, - }), - }) - - // Create some messages for each suggestion - await Promise.all( - Array.from({ length: faker.number.int({ min: 1, max: 3 }) }, () => - tx.serviceSuggestionMessage.create({ - data: generateFakeServiceSuggestionMessage(serviceSuggestion.id, [ - specialUsers.normal.id, - specialUsers.admin.id, - ]), - }) - ) - ) - } - - // Then create 5 EDIT_SERVICE suggestions - await Promise.all( - services.slice(0, 5).map(async (service) => { - const status = faker.helpers.arrayElement(Object.values(ServiceSuggestionStatus)) - const suggestion = await tx.serviceSuggestion.create({ - data: generateFakeServiceSuggestion({ - type: ServiceSuggestionType.EDIT_SERVICE, - status, - userId: specialUsers.normal.id, - serviceId: service.id, - }), - }) - - // Create some messages for each suggestion - await Promise.all( - Array.from({ length: faker.number.int({ min: 0, max: 3 }) }, () => - tx.serviceSuggestionMessage.create({ - data: generateFakeServiceSuggestionMessage(suggestion.id, [ - specialUsers.normal.id, - specialUsers.admin.id, - ]), - }) - ) - ) - }) - ) - - // ---- Create internal notes for users ---- - await Promise.all( - users.map(async (user) => { - // Create 1-3 notes for each user - const numNotes = faker.number.int({ min: 1, max: 3 }) - return Promise.all( - Array.from({ length: numNotes }, () => - tx.internalUserNote.create({ - data: generateFakeInternalNote( - user.id, - faker.helpers.arrayElement([specialUsers.admin.id, specialUsers.verifier.id]) - ), - }) - ) - ) - }) - ) - - // Add some notes to special users as well - await Promise.all( - Object.values(specialUsers).map(async (user) => { - const numNotes = faker.number.int({ min: 1, max: 3 }) - return Promise.all( - Array.from({ length: numNotes }, () => - tx.internalUserNote.create({ - data: generateFakeInternalNote( - user.id, - faker.helpers.arrayElement([specialUsers.admin.id, specialUsers.verifier.id]) - ), - }) - ) - ) - }) - ) - }, - { - timeout: 1000 * 60 * 10, // 10 minutes - } - ) -} - -async function main() { - try { - await runFaker() - - console.info('✅ Fake data generated successfully') - } catch (error) { - console.error('❌ Error generating fake data:', error) - process.exit(1) - } finally { - await prisma.$disconnect() - } -} - -main().catch((error: unknown) => { - console.error('❌ Fatal error:', error) - process.exit(1) -}) diff --git a/web/src/actions/account.ts b/web/src/actions/account.ts deleted file mode 100644 index 38d3ae8..0000000 --- a/web/src/actions/account.ts +++ /dev/null @@ -1,221 +0,0 @@ -import { ActionError } from 'astro:actions' -import { z } from 'astro:content' - -import { karmaUnlocksById } from '../constants/karmaUnlocks' -import { createAccount } from '../lib/accountCreate' -import { captchaFormSchemaProperties, captchaFormSchemaSuperRefine } from '../lib/captchaValidation' -import { defineProtectedAction } from '../lib/defineProtectedAction' -import { saveFileLocally } from '../lib/fileStorage' -import { handleHoneypotTrap } from '../lib/honeypot' -import { startImpersonating } from '../lib/impersonation' -import { makeKarmaUnlockMessage, makeUserWithKarmaUnlocks } from '../lib/karmaUnlocks' -import { prisma } from '../lib/prisma' -import { redisPreGeneratedSecretTokens } from '../lib/redis/redisPreGeneratedSecretTokens' -import { login, logout, setUserSessionIdCookie } from '../lib/userCookies' -import { - generateUserSecretToken, - hashUserSecretToken, - parseUserSecretToken, - USER_SECRET_TOKEN_REGEX, -} from '../lib/userSecretToken' -import { imageFileSchema } from '../lib/zodUtils' - -export const accountActions = { - login: defineProtectedAction({ - accept: 'form', - permissions: 'guest', - input: z.object({ - token: z.string().regex(USER_SECRET_TOKEN_REGEX).transform(parseUserSecretToken), - redirect: z.string().optional(), - }), - handler: async (input, context) => { - await logout(context) - - const tokenHash = hashUserSecretToken(input.token) - const matchedUser = await prisma.user.findFirst({ - where: { - secretTokenHash: tokenHash, - }, - }) - - if (!matchedUser) { - throw new ActionError({ - code: 'UNAUTHORIZED', - message: 'No user exists with this token', - }) - } - - await login(context, makeUserWithKarmaUnlocks(matchedUser)) - - return { - user: matchedUser, - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - redirect: input.redirect || context.request.headers.get('referer') || '/', - } - }, - }), - - preGenerateToken: defineProtectedAction({ - accept: 'form', - permissions: 'guest', - handler: async () => { - const token = generateUserSecretToken() - await redisPreGeneratedSecretTokens.storePreGeneratedToken(token) - return { - token, - } as const - }, - }), - - generate: defineProtectedAction({ - accept: 'form', - permissions: 'guest', - input: z - .object({ - token: z.string().regex(USER_SECRET_TOKEN_REGEX).transform(parseUserSecretToken).optional(), - /** @deprecated Honey pot field, do not use */ - message: z.unknown().optional(), - ...captchaFormSchemaProperties, - }) - .superRefine(captchaFormSchemaSuperRefine), - handler: async (input, context) => { - await handleHoneypotTrap({ - input, - honeyPotTrapField: 'message', - userId: context.locals.user?.id, - location: 'account.generate', - }) - - const isValidToken = input.token - ? await redisPreGeneratedSecretTokens.validateAndConsumePreGeneratedToken(input.token) - : true - if (!isValidToken) { - throw new ActionError({ - code: 'BAD_REQUEST', - message: 'Invalid or expired token', - }) - } - - const { token, user: newUser } = await createAccount(input.token) - await setUserSessionIdCookie(context.cookies, newUser.secretTokenHash) - context.locals.user = makeUserWithKarmaUnlocks(newUser) - - return { - token, - user: newUser, - } as const - }, - }), - - impersonate: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: z.object({ - targetUserId: z.coerce.number().int().positive(), - redirect: z.string().optional(), - }), - handler: async (input, context) => { - const adminUser = context.locals.user - - const targetUser = await prisma.user.findUnique({ - where: { id: input.targetUserId }, - }) - - if (!targetUser) { - throw new ActionError({ - code: 'NOT_FOUND', - message: 'Target user not found', - }) - } - - if (targetUser.admin) { - throw new ActionError({ - code: 'FORBIDDEN', - message: 'Cannot impersonate admin user', - }) - } - - await startImpersonating(context, adminUser, makeUserWithKarmaUnlocks(targetUser)) - - return { - adminUser, - impersonatedUser: targetUser, - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - redirect: input.redirect || context.request.headers.get('referer') || '/', - } - }, - }), - - update: defineProtectedAction({ - accept: 'form', - permissions: 'user', - input: z.object({ - id: z.coerce.number().int().positive(), - displayName: z.string().max(100, 'Display name must be 100 characters or less').optional().nullable(), - link: z - .string() - .url('Must be a valid URL') - .max(255, 'URL must be 255 characters or less') - .optional() - .nullable(), - pictureFile: imageFileSchema, - }), - handler: async (input, context) => { - if (input.id !== context.locals.user.id) { - throw new ActionError({ - code: 'FORBIDDEN', - message: 'You can only update your own profile', - }) - } - - if ( - input.displayName !== undefined && - input.displayName !== context.locals.user.displayName && - !context.locals.user.karmaUnlocks.displayName - ) { - throw new ActionError({ - code: 'FORBIDDEN', - message: makeKarmaUnlockMessage(karmaUnlocksById.displayName), - }) - } - - if ( - input.link !== undefined && - input.link !== context.locals.user.link && - !context.locals.user.karmaUnlocks.websiteLink - ) { - throw new ActionError({ - code: 'FORBIDDEN', - message: makeKarmaUnlockMessage(karmaUnlocksById.websiteLink), - }) - } - - if (input.pictureFile !== undefined && !context.locals.user.karmaUnlocks.profilePicture) { - throw new ActionError({ - code: 'FORBIDDEN', - message: makeKarmaUnlockMessage(karmaUnlocksById.profilePicture), - }) - } - - const pictureUrl = - input.pictureFile && input.pictureFile.size > 0 - ? await saveFileLocally( - input.pictureFile, - input.pictureFile.name, - `users/pictures/${String(context.locals.user.id)}` - ) - : null - - const user = await prisma.user.update({ - where: { id: context.locals.user.id }, - data: { - displayName: input.displayName ?? null, - link: input.link ?? null, - picture: pictureUrl, - }, - }) - - return { user } - }, - }), -} diff --git a/web/src/actions/admin/attribute.ts b/web/src/actions/admin/attribute.ts deleted file mode 100644 index ca25d4b..0000000 --- a/web/src/actions/admin/attribute.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { AttributeCategory, AttributeType } from '@prisma/client' -import { z } from 'astro/zod' -import { ActionError } from 'astro:actions' -import slugify from 'slugify' - -import { defineProtectedAction } from '../../lib/defineProtectedAction' -import { prisma } from '../../lib/prisma' - -import type { Prisma } from '@prisma/client' - -const attributeInputSchema = z.object({ - title: z.string().min(1, 'Title is required'), - description: z.string().min(1, 'Description is required'), - category: z.nativeEnum(AttributeCategory), - type: z.nativeEnum(AttributeType), - privacyPoints: z.coerce.number().int().min(-100).max(100).default(0), - trustPoints: z.coerce.number().int().min(-100).max(100).default(0), - slug: z - .string() - .min(1, 'Slug is required') - .regex(/^[a-z0-9-]+$/, 'Allowed characters: lowercase letters, numbers, and hyphens'), -}) - -const attributeSelect = { - id: true, - slug: true, - title: true, - description: true, - category: true, - type: true, - privacyPoints: true, - trustPoints: true, - createdAt: true, - updatedAt: true, -} satisfies Prisma.AttributeSelect - -export const adminAttributeActions = { - create: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: z.object({ - title: z.string().min(1, 'Title is required'), - description: z.string().min(1, 'Description is required'), - category: z.nativeEnum(AttributeCategory), - type: z.nativeEnum(AttributeType), - privacyPoints: z.coerce.number().int().min(-100).max(100).default(0), - trustPoints: z.coerce.number().int().min(-100).max(100).default(0), - }), - handler: async (input) => { - const slug = slugify(input.title, { lower: true, strict: true }) - - const attribute = await prisma.attribute.create({ - data: { - ...input, - slug, - }, - select: attributeSelect, - }) - return { attribute } - }, - }), - - update: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: attributeInputSchema.extend({ - id: z.coerce.number().int().positive(), - }), - handler: async (input) => { - const { id, title, slug, ...data } = input - - const existingAttribute = await prisma.attribute.findUnique({ - where: { id }, - select: { title: true, slug: true }, - }) - - if (!existingAttribute) { - throw new ActionError({ - code: 'NOT_FOUND', - message: 'Attribute not found', - }) - } - - // Check for slug uniqueness (ignore current attribute) - const slugConflict = await prisma.attribute.findFirst({ - where: { slug, NOT: { id } }, - select: { id: true }, - }) - if (slugConflict) { - throw new ActionError({ - code: 'CONFLICT', - message: 'Slug already in use', - }) - } - - const attribute = await prisma.attribute.update({ - where: { id }, - data: { - title, - slug, - ...data, - }, - select: attributeSelect, - }) - - return { attribute } - }, - }), - - delete: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: z.object({ - id: z.coerce.number().int().positive('Attribute ID must be a positive integer.'), - }), - handler: async ({ id }) => { - try { - await prisma.attribute.delete({ - where: { id }, - }) - return { success: true, message: 'Attribute deleted successfully.' } - } catch (error) { - // Prisma throws an error if the record to delete is not found, - // or if there are related records that prevent deletion (foreign key constraints). - // We can customize the error message based on the type of error if needed. - console.error('Error deleting attribute:', error) - throw new ActionError({ - code: 'INTERNAL_SERVER_ERROR', - message: 'Failed to delete attribute. It might be in use or already deleted.', - }) - } - }, - }), -} diff --git a/web/src/actions/admin/event.ts b/web/src/actions/admin/event.ts deleted file mode 100644 index 7817ffb..0000000 --- a/web/src/actions/admin/event.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { EventType } from '@prisma/client' -import { z } from 'astro/zod' -import { ActionError } from 'astro:actions' - -import { defineProtectedAction } from '../../lib/defineProtectedAction' -import { prisma } from '../../lib/prisma' - -export const adminEventActions = { - create: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: z - .object({ - serviceId: z.coerce.number().int().positive(), - title: z.string().min(1), - content: z.string().min(1), - icon: z.string().optional(), - source: z.string().optional(), - type: z.nativeEnum(EventType).default('NORMAL'), - startedAt: z.coerce.date(), - endedAt: z.coerce.date().optional(), - }) - .superRefine((data, ctx) => { - if (data.endedAt && data.startedAt > data.endedAt) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - path: ['endedAt'], - message: 'Ended at must be after started at', - }) - } - }), - handler: async (input) => { - const event = await prisma.event.create({ - data: { - ...input, - visible: true, - }, - select: { - id: true, - }, - }) - return { event } - }, - }), - - toggle: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: z.object({ - eventId: z.coerce.number().int().positive(), - }), - handler: async (input) => { - const existingEvent = await prisma.event.findUnique({ where: { id: input.eventId } }) - if (!existingEvent) { - throw new ActionError({ - code: 'BAD_REQUEST', - message: 'Event not found', - }) - } - - const event = await prisma.event.update({ - where: { id: input.eventId }, - data: { - visible: !existingEvent.visible, - }, - select: { - id: true, - }, - }) - return { event } - }, - }), - - update: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: z - .object({ - eventId: z.coerce.number().int().positive(), - title: z.string().min(1), - content: z.string().min(1), - icon: z.string().optional(), - source: z.string().optional(), - type: z.nativeEnum(EventType).default('NORMAL'), - startedAt: z.coerce.date(), - endedAt: z.coerce.date().optional(), - }) - .superRefine((data, ctx) => { - if (data.endedAt && data.startedAt > data.endedAt) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - path: ['endedAt'], - message: 'Ended at must be after started at', - }) - } - }), - handler: async (input) => { - const { eventId, ...data } = input - const existingEvent = await prisma.event.findUnique({ where: { id: eventId } }) - if (!existingEvent) { - throw new ActionError({ - code: 'BAD_REQUEST', - message: 'Event not found', - }) - } - - const event = await prisma.event.update({ - where: { id: eventId }, - data, - select: { - id: true, - }, - }) - return { event } - }, - }), - - delete: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: z.object({ - eventId: z.coerce.number().int().positive(), - }), - handler: async (input) => { - const event = await prisma.event.delete({ where: { id: input.eventId } }) - return { event } - }, - }), -} diff --git a/web/src/actions/admin/index.ts b/web/src/actions/admin/index.ts deleted file mode 100644 index 14dff5c..0000000 --- a/web/src/actions/admin/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { adminAttributeActions } from './attribute' -import { adminEventActions } from './event' -import { adminServiceActions } from './service' -import { adminServiceSuggestionActions } from './serviceSuggestion' -import { adminUserActions } from './user' -import { verificationStep } from './verificationStep' - -export const adminActions = { - attribute: adminAttributeActions, - event: adminEventActions, - service: adminServiceActions, - serviceSuggestions: adminServiceSuggestionActions, - user: adminUserActions, - verificationStep, -} diff --git a/web/src/actions/admin/service.ts b/web/src/actions/admin/service.ts deleted file mode 100644 index 1492054..0000000 --- a/web/src/actions/admin/service.ts +++ /dev/null @@ -1,234 +0,0 @@ -import { Currency, ServiceVisibility, VerificationStatus } from '@prisma/client' -import { z } from 'astro/zod' -import { ActionError } from 'astro:actions' -import slugify from 'slugify' - -import { defineProtectedAction } from '../../lib/defineProtectedAction' -import { saveFileLocally } from '../../lib/fileStorage' -import { prisma } from '../../lib/prisma' -import { - imageFileSchema, - stringListOfUrlsSchema, - stringListOfUrlsSchemaRequired, - zodCohercedNumber, -} from '../../lib/zodUtils' - -const serviceSchemaBase = z.object({ - id: z.number(), - slug: z - .string() - .regex(/^[a-z0-9-]+$/, 'Allowed characters: lowercase letters, numbers, and hyphens') - .optional(), - name: z.string().min(1).max(20), - description: z.string().min(1), - serviceUrls: stringListOfUrlsSchemaRequired, - tosUrls: stringListOfUrlsSchemaRequired, - onionUrls: stringListOfUrlsSchema, - kycLevel: z.coerce.number().int().min(0).max(4), - attributes: z.array(z.coerce.number().int().positive()), - categories: z.array(z.coerce.number().int().positive()).min(1), - verificationStatus: z.nativeEnum(VerificationStatus), - verificationSummary: z.string().optional().nullable().default(null), - verificationProofMd: z.string().optional().nullable().default(null), - acceptedCurrencies: z.array(z.nativeEnum(Currency)), - referral: z.string().optional().nullable().default(null), - imageFile: imageFileSchema, - overallScore: zodCohercedNumber(z.number().int().min(0).max(10)).optional(), - serviceVisibility: z.nativeEnum(ServiceVisibility), -}) - -const addSlugIfMissing = < - T extends { - slug?: string | null | undefined - name: string - }, ->( - input: T -) => ({ - ...input, - slug: - input.slug ?? - slugify(input.name, { - lower: true, - strict: true, - remove: /[^a-zA-Z0-9\-._]/g, - replacement: '-', - }), -}) - -const contactMethodSchema = z.object({ - id: z.number().optional(), - label: z.string().min(1).max(50), - value: z.string().min(1).max(200), - iconId: z.string().min(1).max(50), - info: z.string().max(200).optional().default(''), - serviceId: z.number(), -}) - -export const adminServiceActions = { - create: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: serviceSchemaBase.omit({ id: true }).transform(addSlugIfMissing), - handler: async (input) => { - const existing = await prisma.service.findUnique({ - where: { - slug: input.slug, - }, - }) - - if (existing) { - throw new ActionError({ - code: 'CONFLICT', - message: 'A service with this slug already exists', - }) - } - - const { imageFile, ...serviceData } = input - const imageUrl = imageFile ? await saveFileLocally(imageFile, imageFile.name) : undefined - - const service = await prisma.service.create({ - data: { - ...serviceData, - categories: { - connect: input.categories.map((id) => ({ id })), - }, - attributes: { - create: input.attributes.map((attributeId) => ({ - attribute: { - connect: { id: attributeId }, - }, - })), - }, - imageUrl, - }, - select: { - id: true, - slug: true, - }, - }) - - return { service } - }, - }), - - update: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: serviceSchemaBase.transform(addSlugIfMissing), - handler: async (input) => { - const { id, categories, attributes, imageFile, ...data } = input - - const existing = await prisma.service.findUnique({ - where: { - slug: input.slug, - NOT: { id }, - }, - }) - - if (existing) { - throw new ActionError({ - code: 'CONFLICT', - message: 'A service with this slug already exists', - }) - } - - const imageUrl = imageFile ? await saveFileLocally(imageFile, imageFile.name) : undefined - - // Get existing attributes and categories to compute differences - const existingService = await prisma.service.findUnique({ - where: { id }, - include: { - categories: true, - attributes: { - include: { - attribute: true, - }, - }, - }, - }) - - if (!existingService) { - throw new ActionError({ - code: 'NOT_FOUND', - message: 'Service not found', - }) - } - - // Find categories to connect and disconnect - const existingCategoryIds = existingService.categories.map((c) => c.id) - const categoriesToAdd = categories.filter((cId) => !existingCategoryIds.includes(cId)) - const categoriesToRemove = existingCategoryIds.filter((cId) => !categories.includes(cId)) - - // Find attributes to connect and disconnect - const existingAttributeIds = existingService.attributes.map((a) => a.attributeId) - const attributesToAdd = attributes.filter((aId) => !existingAttributeIds.includes(aId)) - const attributesToRemove = existingAttributeIds.filter((aId) => !attributes.includes(aId)) - - const service = await prisma.service.update({ - where: { id }, - data: { - ...data, - imageUrl, - categories: { - connect: categoriesToAdd.map((id) => ({ id })), - disconnect: categoriesToRemove.map((id) => ({ id })), - }, - attributes: { - // Connect new attributes - create: attributesToAdd.map((attributeId) => ({ - attribute: { - connect: { id: attributeId }, - }, - })), - // Delete specific attributes that are no longer needed - deleteMany: attributesToRemove.map((attributeId) => ({ - attributeId, - })), - }, - }, - }) - return { service } - }, - }), - - createContactMethod: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: contactMethodSchema.omit({ id: true }), - handler: async (input) => { - const contactMethod = await prisma.serviceContactMethod.create({ - data: input, - }) - return { contactMethod } - }, - }), - - updateContactMethod: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: contactMethodSchema, - handler: async (input) => { - const { id, ...data } = input - const contactMethod = await prisma.serviceContactMethod.update({ - where: { id }, - data, - }) - return { contactMethod } - }, - }), - - deleteContactMethod: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: z.object({ - id: z.number(), - }), - handler: async (input) => { - await prisma.serviceContactMethod.delete({ - where: { id: input.id }, - }) - return { success: true } - }, - }), -} diff --git a/web/src/actions/admin/serviceSuggestion.ts b/web/src/actions/admin/serviceSuggestion.ts deleted file mode 100644 index 55a7f1d..0000000 --- a/web/src/actions/admin/serviceSuggestion.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { ServiceSuggestionStatus } from '@prisma/client' -import { z } from 'astro/zod' -import { ActionError } from 'astro:actions' - -import { defineProtectedAction } from '../../lib/defineProtectedAction' -import { prisma } from '../../lib/prisma' - -export const adminServiceSuggestionActions = { - update: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: z.object({ - suggestionId: z.coerce.number().int().positive(), - status: z.nativeEnum(ServiceSuggestionStatus), - }), - handler: async (input) => { - const suggestion = await prisma.serviceSuggestion.findUnique({ - select: { - id: true, - status: true, - serviceId: true, - }, - where: { id: input.suggestionId }, - }) - - if (!suggestion) { - throw new ActionError({ - code: 'NOT_FOUND', - message: 'Suggestion not found', - }) - } - - await prisma.serviceSuggestion.update({ - where: { id: suggestion.id }, - data: { - status: input.status, - }, - }) - }, - }), - - message: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: z.object({ - suggestionId: z.coerce.number().int().positive(), - content: z.string().min(1).max(1000), - }), - handler: async (input, context) => { - const suggestion = await prisma.serviceSuggestion.findUnique({ - select: { - id: true, - userId: true, - }, - where: { id: input.suggestionId }, - }) - - if (!suggestion) { - throw new Error('Suggestion not found') - } - - await prisma.serviceSuggestionMessage.create({ - data: { - content: input.content, - suggestionId: suggestion.id, - userId: context.locals.user.id, - }, - }) - }, - }), -} diff --git a/web/src/actions/admin/user.ts b/web/src/actions/admin/user.ts deleted file mode 100644 index c253009..0000000 --- a/web/src/actions/admin/user.ts +++ /dev/null @@ -1,288 +0,0 @@ -import { type Prisma, type ServiceUserRole, type PrismaClient } from '@prisma/client' -import { ActionError } from 'astro:actions' -import { z } from 'zod' - -import { defineProtectedAction } from '../../lib/defineProtectedAction' -import { saveFileLocally } from '../../lib/fileStorage' -import { prisma as prismaInstance } from '../../lib/prisma' - -const prisma = prismaInstance as PrismaClient - -const selectUserReturnFields = { - id: true, - name: true, - displayName: true, - link: true, - picture: true, - admin: true, - verified: true, - verifier: true, - verifiedLink: true, - secretTokenHash: true, - totalKarma: true, - createdAt: true, - updatedAt: true, - spammer: true, -} as const satisfies Prisma.UserSelect - -export const adminUserActions = { - search: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: z.object({ - name: z.string().min(1, 'User name is required'), - }), - handler: async (input) => { - const user = await prisma.user.findUnique({ - where: { name: input.name }, - select: selectUserReturnFields, - }) - - return { user } - }, - }), - - update: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: z.object({ - id: z.number().int().positive(), - name: z.string().min(1, 'Name is required').max(255, 'Name must be less than 255 characters'), - link: z - .string() - .url('Invalid URL') - .nullable() - .default(null) // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - .transform((val) => val || null), - picture: z.string().max(255, 'Picture URL must be less than 255 characters').nullable().default(null), - pictureFile: z.instanceof(File).optional(), - verifier: z.boolean().default(false), - admin: z.boolean().default(false), - spammer: z.boolean().default(false), - verifiedLink: z - .string() - .url('Invalid URL') - .nullable() - .default(null) // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - .transform((val) => val || null), - displayName: z - .string() - .max(50, 'Display Name must be less than 50 characters') - .nullable() - .default(null) // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - .transform((val) => val || null), - }), - handler: async ({ id, picture, pictureFile, ...valuesToUpdate }) => { - const user = await prisma.user.findUnique({ - where: { - id, - }, - select: { - id: true, - }, - }) - - if (!user) { - throw new ActionError({ - code: 'BAD_REQUEST', - message: 'User not found', - }) - } - - let pictureUrl = picture ?? null - if (pictureFile && pictureFile.size > 0) { - pictureUrl = await saveFileLocally(pictureFile, pictureFile.name, 'users/pictures/') - } - - const updatedUser = await prisma.user.update({ - where: { id: user.id }, - data: { - ...valuesToUpdate, - verified: !!valuesToUpdate.verifiedLink, - picture: pictureUrl, - }, - select: selectUserReturnFields, - }) - - return { - updatedUser, - } - }, - }), - - internalNotes: { - add: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: z.object({ - userId: z.coerce.number().int().positive(), - content: z.string().min(1).max(1000), - }), - handler: async (input, context) => { - const note = await prisma.internalUserNote.create({ - data: { - content: input.content, - userId: input.userId, - addedByUserId: context.locals.user.id, - }, - select: { - id: true, - }, - }) - - return { note } - }, - }), - - delete: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: z.object({ - noteId: z.coerce.number().int().positive(), - }), - handler: async (input) => { - await prisma.internalUserNote.delete({ - where: { - id: input.noteId, - }, - }) - }, - }), - - update: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: z.object({ - noteId: z.coerce.number().int().positive(), - content: z.string().min(1).max(1000), - }), - handler: async (input, context) => { - const note = await prisma.internalUserNote.update({ - where: { - id: input.noteId, - }, - data: { - content: input.content, - addedByUserId: context.locals.user.id, - }, - select: { - id: true, - }, - }) - - return { note } - }, - }), - }, - - serviceAffiliations: { - add: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: z.object({ - userId: z.coerce.number().int().positive(), - serviceId: z.coerce.number().int().positive(), - role: z.enum(['OWNER', 'ADMIN', 'MODERATOR', 'SUPPORT', 'TEAM_MEMBER']), - }), - handler: async (input) => { - // Check if the user exists - const user = await prisma.user.findUnique({ - where: { id: input.userId }, - select: { id: true }, - }) - - if (!user) { - throw new ActionError({ - code: 'BAD_REQUEST', - message: 'User not found', - }) - } - - // Check if the service exists - const service = await prisma.service.findUnique({ - where: { id: input.serviceId }, - select: { id: true, name: true }, - }) - - if (!service) { - throw new ActionError({ - code: 'BAD_REQUEST', - message: 'Service not found', - }) - } - - try { - // Check if the service affiliation already exists - const existingAffiliation = await prisma.serviceUser.findUnique({ - where: { - userId_serviceId: { - userId: input.userId, - serviceId: input.serviceId, - }, - }, - }) - - let serviceAffiliation - - if (existingAffiliation) { - // Update existing affiliation - serviceAffiliation = await prisma.serviceUser.update({ - where: { - userId_serviceId: { - userId: input.userId, - serviceId: input.serviceId, - }, - }, - data: { - role: input.role as ServiceUserRole, - }, - }) - - return { serviceAffiliation, serviceName: service.name, updated: true } - } else { - // Create new affiliation - serviceAffiliation = await prisma.serviceUser.create({ - data: { - userId: input.userId, - serviceId: input.serviceId, - role: input.role as ServiceUserRole, - }, - }) - - return { serviceAffiliation, serviceName: service.name } - } - } catch (error) { - console.error('Error managing service affiliation:', error) - throw new ActionError({ - code: 'INTERNAL_SERVER_ERROR', - message: 'Error managing service affiliation', - }) - } - }, - }), - - remove: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: z.object({ - id: z.coerce.number().int().positive(), - }), - handler: async (input) => { - const serviceAffiliation = await prisma.serviceUser.delete({ - where: { - id: input.id, - }, - include: { - service: { - select: { - name: true, - }, - }, - }, - }) - - return { serviceAffiliation } - }, - }), - }, -} diff --git a/web/src/actions/admin/verificationStep.ts b/web/src/actions/admin/verificationStep.ts deleted file mode 100644 index 0552e20..0000000 --- a/web/src/actions/admin/verificationStep.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { VerificationStepStatus } from '@prisma/client' -import { z } from 'astro/zod' -import { ActionError } from 'astro:actions' - -import { defineProtectedAction } from '../../lib/defineProtectedAction' -import { prisma } from '../../lib/prisma' - -const verificationStepSchemaBase = z.object({ - title: z.string().min(1, 'Title is required'), - description: z - .string() - .min(1, 'Description is required') - .max(200, 'Description must be 200 characters or less'), - status: z.nativeEnum(VerificationStepStatus), - serviceId: z.coerce.number().int().positive(), - evidenceMd: z.string().optional().nullable().default(null), -}) - -const verificationStepUpdateSchema = z.object({ - id: z.coerce.number().int().positive(), - title: z.string().min(1, 'Title is required').optional(), - description: z - .string() - .min(1, 'Description is required') - .max(200, 'Description must be 200 characters or less') - .optional(), - status: z.nativeEnum(VerificationStepStatus).optional(), - evidenceMd: z.string().optional().nullable(), -}) - -const verificationStepIdSchema = z.object({ - id: z.coerce.number().int().positive(), -}) - -export const verificationStep = { - create: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: verificationStepSchemaBase, - handler: async (input) => { - const { serviceId, title, description, status, evidenceMd } = input - - const service = await prisma.service.findUnique({ - where: { id: serviceId }, - }) - - if (!service) { - throw new ActionError({ - code: 'NOT_FOUND', - message: 'Service not found', - }) - } - - const newVerificationStep = await prisma.verificationStep.create({ - data: { - title, - description, - status, - evidenceMd, - service: { - connect: { id: serviceId }, - }, - }, - }) - - return { verificationStep: newVerificationStep } - }, - }), - - update: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: verificationStepUpdateSchema, - handler: async (input) => { - const { id, ...dataToUpdate } = input - - const existingStep = await prisma.verificationStep.findUnique({ - where: { id }, - }) - - if (!existingStep) { - throw new ActionError({ - code: 'NOT_FOUND', - message: 'Verification step not found', - }) - } - - const updatedVerificationStep = await prisma.verificationStep.update({ - where: { id }, - data: dataToUpdate, - }) - - return { verificationStep: updatedVerificationStep } - }, - }), - - delete: defineProtectedAction({ - accept: 'form', - permissions: 'admin', - input: verificationStepIdSchema, - handler: async ({ id }) => { - const existingStep = await prisma.verificationStep.findUnique({ - where: { id }, - }) - - if (!existingStep) { - throw new ActionError({ - code: 'NOT_FOUND', - message: 'Verification step not found', - }) - } - - await prisma.verificationStep.delete({ where: { id } }) - - return { success: true, deletedId: id } - }, - }), -} diff --git a/web/src/actions/comment.ts b/web/src/actions/comment.ts deleted file mode 100644 index 78e1747..0000000 --- a/web/src/actions/comment.ts +++ /dev/null @@ -1,442 +0,0 @@ -import crypto from 'crypto' - -import { ActionError } from 'astro:actions' -import { z } from 'astro:schema' -import { formatDistanceStrict } from 'date-fns' - -import { karmaUnlocksById } from '../constants/karmaUnlocks' -import { defineProtectedAction } from '../lib/defineProtectedAction' -import { handleHoneypotTrap } from '../lib/honeypot' -import { makeKarmaUnlockMessage } from '../lib/karmaUnlocks' -import { getOrCreateNotificationPreferences } from '../lib/notificationPreferences' -import { prisma } from '../lib/prisma' -import { timeTrapSecretKey } from '../lib/timeTrapSecret' - -import type { CommentStatus, Prisma } from '@prisma/client' - -const COMMENT_RATE_LIMIT_WINDOW_MINUTES = 5 -const MAX_COMMENTS_PER_WINDOW = 1 -const MAX_COMMENTS_PER_WINDOW_VERIFIED_USER = 5 - -export const commentActions = { - vote: defineProtectedAction({ - accept: 'form', - permissions: 'user', - input: z.object({ - commentId: z.coerce.number().int().positive(), - downvote: z.coerce.boolean(), - }), - handler: async (input, context) => { - try { - // Check user karma requirement - if (!context.locals.user.karmaUnlocks.voteComments) { - throw new ActionError({ - code: 'FORBIDDEN', - message: makeKarmaUnlockMessage(karmaUnlocksById.voteComments), - }) - } - - // Handle the vote in a transaction - await prisma.$transaction(async (tx) => { - // Get existing vote if any - const existingVote = await tx.commentVote.findUnique({ - where: { - commentId_userId: { - commentId: input.commentId, - userId: context.locals.user.id, - }, - }, - }) - - if (existingVote) { - // If vote type is the same, remove the vote - if (existingVote.downvote === input.downvote) { - await tx.commentVote.delete({ - where: { id: existingVote.id }, - }) - } else { - // If vote type is different, update the vote - await tx.commentVote.update({ - where: { id: existingVote.id }, - data: { downvote: input.downvote }, - }) - } - } else { - // Create new vote - await tx.commentVote.create({ - data: { - downvote: input.downvote, - commentId: input.commentId, - userId: context.locals.user.id, - }, - }) - } - }) - - return true - } catch (error) { - if (error instanceof ActionError) throw error - - console.error('Error voting on comment:', error) - throw new ActionError({ - code: 'INTERNAL_SERVER_ERROR', - message: 'Error voting on comment', - }) - } - }, - }), - - create: defineProtectedAction({ - accept: 'form', - permissions: 'user', - input: z - .object({ - content: z.string().min(10).max(2000), - serviceId: z.coerce.number().int().positive(), - parentId: z.coerce.number().optional(), - /** @deprecated Honey pot field, do not use */ - message: z.unknown().optional(), - rating: z.coerce.number().int().min(1).max(5).optional(), - encTimestamp: z.string().min(1), // time trap field - internalNote: z.string().max(500).optional(), - issueKycRequested: z.coerce.boolean().optional(), - issueFundsBlocked: z.coerce.boolean().optional(), - issueScam: z.coerce.boolean().optional(), - issueDetails: z.string().max(120).optional(), - orderId: z.string().max(100).optional(), - }) - .superRefine((data, ctx) => { - if (data.rating && data.parentId) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - path: ['parentId'], - message: 'Ratings cannot be provided for replies', - }) - } - if (!data.parentId) { - if (data.content.length < 30) { - ctx.addIssue({ - code: z.ZodIssueCode.too_small, - minimum: 30, - type: 'string', - inclusive: true, - path: ['content'], - message: 'Content must be at least 30 characters', - }) - } - } - }), - handler: async (input, context) => { - if (context.locals.user.karmaUnlocks.commentsDisabled) { - throw new ActionError({ - code: 'FORBIDDEN', - message: makeKarmaUnlockMessage(karmaUnlocksById.commentsDisabled), - }) - } - - await handleHoneypotTrap({ - input, - honeyPotTrapField: 'message', - userId: context.locals.user.id, - location: 'comment.create', - }) - - // --- Time Trap Validation Start --- - try { - const algorithm = 'aes-256-cbc' - const decodedValue = Buffer.from(input.encTimestamp, 'base64').toString('utf8') - const [ivHex, encryptedHex] = decodedValue.split(':') - - if (!ivHex || !encryptedHex) { - throw new Error('Invalid time trap format.') - } - - const iv = Buffer.from(ivHex, 'hex') - const decipher = crypto.createDecipheriv(algorithm, timeTrapSecretKey, iv) - let decrypted = decipher.update(encryptedHex, 'hex', 'utf8') - decrypted += decipher.final('utf8') - - const originalTimestamp = parseInt(decrypted, 10) - if (isNaN(originalTimestamp)) { - throw new Error('Invalid timestamp data.') - } - - const now = Date.now() - const timeDiff = now - originalTimestamp - const minTimeSeconds = 2 // 2 seconds - const maxTimeMinutes = 60 // 1 hour - - if (timeDiff < minTimeSeconds * 1000 || timeDiff > maxTimeMinutes * 60 * 1000) { - console.warn(`Time trap triggered: ${(timeDiff / 1000).toLocaleString()}s`) - throw new Error('Invalid submission timing.') - } - } catch (err) { - console.error('Time trap validation failed:', err instanceof Error ? err.message : 'Unknown error') - throw new ActionError({ - code: 'BAD_REQUEST', - message: 'Invalid request', - }) - } - // --- Time Trap Validation End --- - - // --- Rate Limit Check Start --- - const isVerifiedUser = context.locals.user.admin || context.locals.user.verified - const maxCommentsPerWindow = isVerifiedUser - ? MAX_COMMENTS_PER_WINDOW_VERIFIED_USER - : MAX_COMMENTS_PER_WINDOW - - const windowStart = new Date(Date.now() - COMMENT_RATE_LIMIT_WINDOW_MINUTES * 60 * 1000) - const recentCommentCount = await prisma.comment.findMany({ - where: { - authorId: context.locals.user.id, - createdAt: { - gte: windowStart, - }, - }, - select: { - id: true, - createdAt: true, - }, - }) - - if (recentCommentCount.length >= maxCommentsPerWindow) { - const oldestCreatedAt = recentCommentCount.reduce((oldestDate, comment) => { - if (!oldestDate) return comment.createdAt - if (comment.createdAt < oldestDate) return comment.createdAt - return oldestDate - }, null) - - console.warn(`Rate limit exceeded for user ${context.locals.user.id.toLocaleString()}`) - throw new ActionError({ - code: 'TOO_MANY_REQUESTS', // Use specific 429 code - message: `Rate limit exceeded. Please wait ${oldestCreatedAt ? `${formatDistanceStrict(oldestCreatedAt, windowStart)} ` : ''}before commenting again.`, - }) - } - // --- Rate Limit Check End --- - - // --- Format Internal Note from Issue Reports --- - let formattedInternalNote: string | null = null - // Track if this is an issue report - const isIssueReport = - input.issueKycRequested === true || input.issueFundsBlocked === true || input.issueScam === true - - if (isIssueReport) { - const issueTypes = [] - if (input.issueKycRequested) issueTypes.push('KYC REQUESTED') - if (input.issueFundsBlocked) issueTypes.push('FUNDS BLOCKED') - if (input.issueScam) issueTypes.push('POTENTIAL SCAM') - - const details = input.issueDetails?.trim() ?? '' - - formattedInternalNote = `[${issueTypes.join(', ')}]${details ? `: ${details}` : ''}` - } else if (input.internalNote?.trim()) { - formattedInternalNote = input.internalNote.trim() - } - - // Determine if admin review is needed (always true for issue reports) - const requiresAdminReview = isIssueReport || !!(formattedInternalNote && !context.locals.user.admin) - - try { - await prisma.$transaction(async (tx) => { - // First deactivate any existing ratings if providing a new rating - if (input.rating) { - await tx.comment.updateMany({ - where: { - serviceId: input.serviceId, - authorId: context.locals.user.id, - rating: { not: null }, - }, - data: { - ratingActive: false, - }, - }) - } - - // Check for existing orderId for this service if provided - if (input.orderId?.trim()) { - const existingOrderId = await tx.comment.findFirst({ - where: { - serviceId: input.serviceId, - orderId: input.orderId.trim(), - }, - select: { id: true }, - }) - - if (existingOrderId) { - throw new ActionError({ - code: 'BAD_REQUEST', - message: 'This Order ID has already been reported for this service.', - }) - } - } - - // Prepare data object with proper type safety - const commentData: Prisma.CommentCreateInput = { - content: input.content, - service: { connect: { id: input.serviceId } }, - author: { connect: { id: context.locals.user.id } }, - - // Change status to HUMAN_PENDING if there's an issue report, this is so that the AI worker does not pick it up for review - status: context.locals.user.admin ? 'APPROVED' : isIssueReport ? 'HUMAN_PENDING' : 'PENDING', - requiresAdminReview, - orderId: input.orderId?.trim() ?? null, - kycRequested: input.issueKycRequested === true, - fundsBlocked: input.issueFundsBlocked === true, - } - - if (input.parentId) { - commentData.parent = { connect: { id: input.parentId } } - } - - if (input.rating) { - commentData.rating = input.rating - commentData.ratingActive = true - } - - if (formattedInternalNote) { - commentData.internalNote = formattedInternalNote - } - - const newComment = await tx.comment.create({ - data: commentData, - }) - - const notiPref = await getOrCreateNotificationPreferences( - context.locals.user.id, - { enableAutowatchMyComments: true }, - tx - ) - - if (notiPref.enableAutowatchMyComments) { - await tx.notificationPreferences.update({ - where: { userId: context.locals.user.id }, - data: { - watchedComments: { connect: { id: newComment.id } }, - }, - }) - } - }) - - return { success: true } - } catch (error) { - if (error instanceof ActionError) throw error - - console.error('Error creating comment:', error) - throw new ActionError({ - code: 'INTERNAL_SERVER_ERROR', - message: 'Error creating comment', - }) - } - }, - }), - - moderate: defineProtectedAction({ - permissions: ['admin', 'verifier'], - input: z.object({ - commentId: z.number(), - userId: z.number(), - action: z.enum([ - 'status', - 'suspicious', - 'requires-admin-review', - 'community-note', - 'internal-note', - 'private-context', - 'order-id-status', - 'kyc-requested', - 'funds-blocked', - ]), - value: z.union([ - z.enum(['PENDING', 'APPROVED', 'VERIFIED', 'REJECTED']), - z.enum(['PENDING', 'APPROVED', 'REJECTED']), - z.boolean(), - z.string(), - ]), - }), - handler: async (input) => { - try { - const comment = await prisma.comment.findUnique({ - where: { id: input.commentId }, - select: { - id: true, - rating: true, - serviceId: true, - createdAt: true, - authorId: true, - }, - }) - - if (!comment) { - throw new ActionError({ - code: 'NOT_FOUND', - message: 'Comment not found', - }) - } - - const updateData: Prisma.CommentUpdateInput = {} - - switch (input.action) { - case 'status': - updateData.status = input.value as CommentStatus - break - case 'suspicious': { - const isSpam = !!input.value - updateData.suspicious = isSpam - updateData.ratingActive = false - - if (!isSpam && comment.rating) { - const newestRatingOrActiveRating = await prisma.comment.findFirst({ - where: { - serviceId: comment.serviceId, - authorId: comment.authorId, - id: { not: input.commentId }, - rating: { not: null }, - OR: [{ createdAt: { gt: comment.createdAt } }, { ratingActive: true }], - }, - }) - updateData.ratingActive = !newestRatingOrActiveRating - } - break - } - case 'requires-admin-review': - updateData.requiresAdminReview = !!input.value - break - case 'community-note': - updateData.communityNote = input.value as string - break - case 'internal-note': - updateData.internalNote = input.value as string - break - case 'private-context': - updateData.privateContext = input.value as string - break - case 'order-id-status': - updateData.orderIdStatus = input.value as 'APPROVED' | 'PENDING' | 'REJECTED' - break - case 'kyc-requested': - updateData.kycRequested = !!input.value - break - case 'funds-blocked': - updateData.fundsBlocked = !!input.value - break - } - - // Update the comment - await prisma.comment.update({ - where: { id: input.commentId }, - data: updateData, - }) - - return { success: true } - } catch (error) { - if (error instanceof ActionError) throw error - - console.error('Error moderating comment:', error) - throw new ActionError({ - code: 'INTERNAL_SERVER_ERROR', - message: 'Error moderating comment', - }) - } - }, - }), -} diff --git a/web/src/actions/index.ts b/web/src/actions/index.ts deleted file mode 100644 index 7b40a69..0000000 --- a/web/src/actions/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { accountActions } from './account' -import { adminActions } from './admin' -import { commentActions } from './comment' -import { notificationActions } from './notifications' -import { serviceActions } from './service' -import { serviceSuggestionActions } from './serviceSuggestion' - -/** - * @deprecated Don't import this object, use {@link actions} instead, like: `import { actions } from 'astro:actions'` - * - * @example - * ```ts - * import { actions } from 'astro:actions' - * import { server } from '~/actions' // WRONG!!!! - * - * const result = Astro.getActionResult(actions.admin.attribute.create) - * ``` - */ -export const server = { - account: accountActions, - admin: adminActions, - comment: commentActions, - notification: notificationActions, - service: serviceActions, - serviceSuggestion: serviceSuggestionActions, -} - -// Don't create an object named actions, put the actions in the server object instead. Astro will automatically export the server object as actions. diff --git a/web/src/actions/notifications.ts b/web/src/actions/notifications.ts deleted file mode 100644 index 3ae200c..0000000 --- a/web/src/actions/notifications.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { z } from 'astro:content' - -import { defineProtectedAction } from '../lib/defineProtectedAction' -import { prisma } from '../lib/prisma' - -export const notificationActions = { - updateReadStatus: defineProtectedAction({ - accept: 'form', - permissions: 'user', - input: z.object({ - notificationId: z.literal('all').or(z.coerce.number().int().positive()), - read: z.coerce.boolean(), - }), - handler: async (input, context) => { - await prisma.notification.updateMany({ - where: - input.notificationId === 'all' - ? { userId: context.locals.user.id, read: !input.read } - : { userId: context.locals.user.id, id: input.notificationId }, - data: { - read: input.read, - }, - }) - }, - }), - preferences: { - update: defineProtectedAction({ - accept: 'form', - permissions: 'user', - input: z.object({ - enableOnMyCommentStatusChange: z.coerce.boolean().optional(), - enableAutowatchMyComments: z.coerce.boolean().optional(), - enableNotifyPendingRepliesOnWatch: z.coerce.boolean().optional(), - }), - handler: async (input, context) => { - await prisma.notificationPreferences.upsert({ - where: { userId: context.locals.user.id }, - update: { - enableOnMyCommentStatusChange: input.enableOnMyCommentStatusChange, - enableAutowatchMyComments: input.enableAutowatchMyComments, - enableNotifyPendingRepliesOnWatch: input.enableNotifyPendingRepliesOnWatch, - }, - create: { - userId: context.locals.user.id, - enableOnMyCommentStatusChange: input.enableOnMyCommentStatusChange, - enableAutowatchMyComments: input.enableAutowatchMyComments, - enableNotifyPendingRepliesOnWatch: input.enableNotifyPendingRepliesOnWatch, - }, - }) - }, - }), - - watchComment: defineProtectedAction({ - accept: 'form', - permissions: 'user', - input: z.object({ - commentId: z.coerce.number().int().positive(), - watch: z.coerce.boolean(), - }), - handler: async (input, context) => { - await prisma.notificationPreferences.upsert({ - where: { userId: context.locals.user.id }, - update: { - watchedComments: input.watch - ? { connect: { id: input.commentId } } - : { disconnect: { id: input.commentId } }, - }, - create: { - userId: context.locals.user.id, - watchedComments: input.watch ? { connect: { id: input.commentId } } : undefined, - }, - }) - }, - }), - - watchService: defineProtectedAction({ - accept: 'form', - permissions: 'user', - input: z.object({ - serviceId: z.coerce.number().int().positive(), - watchType: z.enum(['all', 'comments', 'events', 'verification']), - value: z.coerce.boolean(), - }), - handler: async (input, context) => { - await prisma.notificationPreferences.upsert({ - where: { userId: context.locals.user.id }, - update: { - onEventCreatedForServices: - input.watchType === 'events' || input.watchType === 'all' - ? input.value - ? { connect: { id: input.serviceId } } - : { disconnect: { id: input.serviceId } } - : undefined, - onRootCommentCreatedForServices: - input.watchType === 'comments' || input.watchType === 'all' - ? input.value - ? { connect: { id: input.serviceId } } - : { disconnect: { id: input.serviceId } } - : undefined, - onVerificationChangeForServices: - input.watchType === 'verification' || input.watchType === 'all' - ? input.value - ? { connect: { id: input.serviceId } } - : { disconnect: { id: input.serviceId } } - : undefined, - }, - create: { - userId: context.locals.user.id, - onEventCreatedForServices: - input.watchType === 'events' || input.watchType === 'all' - ? input.value - ? { connect: { id: input.serviceId } } - : undefined - : undefined, - onRootCommentCreatedForServices: - input.watchType === 'comments' || input.watchType === 'all' - ? input.value - ? { connect: { id: input.serviceId } } - : undefined - : undefined, - onVerificationChangeForServices: - input.watchType === 'verification' || input.watchType === 'all' - ? input.value - ? { connect: { id: input.serviceId } } - : undefined - : undefined, - }, - }) - }, - }), - }, -} diff --git a/web/src/actions/service.ts b/web/src/actions/service.ts deleted file mode 100644 index 8ff9dfd..0000000 --- a/web/src/actions/service.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { z } from 'astro/zod' -import { ActionError } from 'astro:actions' - -import { defineProtectedAction } from '../lib/defineProtectedAction' -import { prisma } from '../lib/prisma' - -export const serviceActions = { - requestVerification: defineProtectedAction({ - accept: 'form', - permissions: 'user', - input: z.object({ - serviceId: z.coerce.number().int().positive(), - action: z.enum(['request', 'withdraw']), - }), - handler: async (input, context) => { - const service = await prisma.service.findUnique({ - where: { - id: input.serviceId, - }, - select: { - verificationStatus: true, - }, - }) - - if (!service) { - throw new ActionError({ - message: 'Service not found', - code: 'NOT_FOUND', - }) - } - - if ( - service.verificationStatus === 'VERIFICATION_SUCCESS' || - service.verificationStatus === 'VERIFICATION_FAILED' - ) { - throw new ActionError({ - message: 'Service is already verified or marked as scam', - code: 'BAD_REQUEST', - }) - } - - const existingRequest = await prisma.serviceVerificationRequest.findUnique({ - where: { - serviceId_userId: { - serviceId: input.serviceId, - userId: context.locals.user.id, - }, - }, - select: { - id: true, - }, - }) - - switch (input.action) { - case 'withdraw': { - if (!existingRequest) { - throw new ActionError({ - message: 'You have not requested verification for this service', - code: 'BAD_REQUEST', - }) - } - await prisma.serviceVerificationRequest.delete({ - where: { - id: existingRequest.id, - }, - }) - break - } - default: - case 'request': { - if (existingRequest) { - throw new ActionError({ - message: 'You have already requested verification for this service', - code: 'BAD_REQUEST', - }) - } - - await prisma.serviceVerificationRequest.create({ - data: { - serviceId: input.serviceId, - userId: context.locals.user.id, - }, - }) - - await prisma.notificationPreferences.upsert({ - where: { userId: context.locals.user.id }, - update: { - onVerificationChangeForServices: { - connect: { id: input.serviceId }, - }, - }, - create: { - userId: context.locals.user.id, - onVerificationChangeForServices: { - connect: { id: input.serviceId }, - }, - }, - }) - break - } - } - }, - }), -} diff --git a/web/src/actions/serviceSuggestion.ts b/web/src/actions/serviceSuggestion.ts deleted file mode 100644 index 8baa39b..0000000 --- a/web/src/actions/serviceSuggestion.ts +++ /dev/null @@ -1,359 +0,0 @@ -import { - Currency, - ServiceSuggestionStatus, - ServiceSuggestionType, - ServiceVisibility, - VerificationStatus, -} from '@prisma/client' -import { z } from 'astro/zod' -import { ActionError } from 'astro:actions' -import { formatDistanceStrict } from 'date-fns' - -import { captchaFormSchemaProperties, captchaFormSchemaSuperRefine } from '../lib/captchaValidation' -import { defineProtectedAction } from '../lib/defineProtectedAction' -import { saveFileLocally } from '../lib/fileStorage' -import { handleHoneypotTrap } from '../lib/honeypot' -import { prisma } from '../lib/prisma' -import { - imageFileSchemaRequired, - stringListOfUrlsSchema, - stringListOfUrlsSchemaRequired, - zodCohercedNumber, -} from '../lib/zodUtils' - -import type { Prisma } from '@prisma/client' - -const SUGGESTION_MESSAGE_RATE_LIMIT_WINDOW_MINUTES = 1 -const MAX_SUGGESTION_MESSAGES_PER_WINDOW = 5 - -export const SUGGESTION_NOTES_MAX_LENGTH = 1000 -export const SUGGESTION_NAME_MAX_LENGTH = 20 -export const SUGGESTION_SLUG_MAX_LENGTH = 20 -export const SUGGESTION_DESCRIPTION_MAX_LENGTH = 100 -export const SUGGESTION_MESSAGE_CONTENT_MAX_LENGTH = 1000 - -const findPossibleDuplicates = async (input: { name: string }) => { - const possibleDuplicates = await prisma.service.findMany({ - where: { - name: { - contains: input.name, - mode: 'insensitive', - }, - }, - select: { - id: true, - name: true, - slug: true, - description: true, - }, - }) - - return possibleDuplicates -} - -const serializeExtraNotes = >( - input: T, - skipKeys: (keyof T)[] = [] -): string => { - return Object.entries(input) - .filter(([key]) => !skipKeys.includes(key as keyof T)) - .map(([key, value]) => { - let serializedValue = '' - if (typeof value === 'string') { - serializedValue = value - } else if (value === undefined || value === null) { - serializedValue = '' - } else if (typeof value === 'object' && 'toString' in value && typeof value.toString === 'function') { - // eslint-disable-next-line @typescript-eslint/no-base-to-string - serializedValue = value.toString() - } else { - try { - serializedValue = JSON.stringify(value) - } catch (error) { - serializedValue = `Error serializing value: ${error instanceof Error ? error.message : 'Unknown error'}` - } - } - return `- ${key}: ${serializedValue}` - }) - .join('\n') -} - -export const serviceSuggestionActions = { - editService: defineProtectedAction({ - accept: 'form', - permissions: 'not-spammer', - input: z - .object({ - notes: z.string().max(SUGGESTION_NOTES_MAX_LENGTH).optional(), - serviceId: z.coerce.number().int().positive(), - extraNotes: z.string().optional(), - /** @deprecated Honey pot field, do not use */ - message: z.unknown().optional(), - ...captchaFormSchemaProperties, - }) - .superRefine(captchaFormSchemaSuperRefine), - handler: async (input, context) => { - await handleHoneypotTrap({ - input, - honeyPotTrapField: 'message', - userId: context.locals.user.id, - location: 'serviceSuggestion.editService', - }) - - const service = await prisma.service.findUnique({ - select: { - id: true, - slug: true, - }, - where: { id: input.serviceId }, - }) - - if (!service) { - throw new ActionError({ - message: 'Service not found', - code: 'BAD_REQUEST', - }) - } - - // Combine notes and extraNotes if available - const combinedNotes = input.extraNotes - ? `${input.notes ?? ''}\n\nSuggested changes:\n${input.extraNotes}` - : input.notes - - const serviceSuggestion = await prisma.serviceSuggestion.create({ - data: { - type: ServiceSuggestionType.EDIT_SERVICE, - notes: combinedNotes, - status: ServiceSuggestionStatus.PENDING, - userId: context.locals.user.id, - serviceId: service.id, - }, - select: { - id: true, - }, - }) - - return { serviceSuggestion, service } - }, - }), - createService: defineProtectedAction({ - accept: 'form', - permissions: 'not-spammer', - input: z - .object({ - notes: z.string().max(SUGGESTION_NOTES_MAX_LENGTH).optional(), - name: z.string().min(1).max(SUGGESTION_NAME_MAX_LENGTH), - slug: z - .string() - .min(1) - .max(SUGGESTION_SLUG_MAX_LENGTH) - .regex(/^[a-z0-9-]+$/, { - message: 'Slug must contain only lowercase letters, numbers, and hyphens', - }) - .refine( - async (slug) => { - const exists = await prisma.service.findUnique({ - select: { id: true }, - where: { slug }, - }) - return !exists - }, - { message: 'Slug must be unique, try a different one' } - ), - description: z.string().min(1).max(SUGGESTION_DESCRIPTION_MAX_LENGTH), - serviceUrls: stringListOfUrlsSchemaRequired, - tosUrls: stringListOfUrlsSchemaRequired, - onionUrls: stringListOfUrlsSchema, - kycLevel: zodCohercedNumber(z.coerce.number().int().min(0).max(4)), - attributes: z.array(z.coerce.number().int().positive()), - categories: z.array(z.coerce.number().int().positive()).min(1), - acceptedCurrencies: z.array(z.nativeEnum(Currency)).min(1), - imageFile: imageFileSchemaRequired, - /** @deprecated Honey pot field, do not use */ - message: z.unknown().optional(), - skipDuplicateCheck: z - .string() - .optional() - .nullable() - .transform((value) => value === 'true'), - ...captchaFormSchemaProperties, - }) - .superRefine(captchaFormSchemaSuperRefine), - - handler: async (input, context) => { - await handleHoneypotTrap({ - input, - honeyPotTrapField: 'message', - userId: context.locals.user.id, - location: 'serviceSuggestion.createService', - }) - - if (!input.skipDuplicateCheck) { - const possibleDuplicates = await findPossibleDuplicates(input) - - if (possibleDuplicates.length > 0) { - return { - hasDuplicates: true, - possibleDuplicates, - extraNotes: serializeExtraNotes(input, [ - 'skipDuplicateCheck', - 'message', - 'imageFile', - 'captcha-value', - 'captcha-solution-hash', - ]), - serviceSuggestion: undefined, - service: undefined, - } as const - } - } - - const imageUrl = await saveFileLocally(input.imageFile, input.imageFile.name) - - const { serviceSuggestion, service } = await prisma.$transaction(async (tx) => { - const serviceSelect = { - id: true, - slug: true, - } satisfies Prisma.ServiceSelect - - const service = await tx.service.create({ - data: { - name: input.name, - slug: input.slug, - description: input.description, - serviceUrls: input.serviceUrls, - tosUrls: input.tosUrls, - onionUrls: input.onionUrls, - kycLevel: input.kycLevel, - acceptedCurrencies: input.acceptedCurrencies, - imageUrl, - verificationStatus: VerificationStatus.COMMUNITY_CONTRIBUTED, - overallScore: 0, - privacyScore: 0, - trustScore: 0, - listedAt: new Date(), - serviceVisibility: ServiceVisibility.UNLISTED, - categories: { - connect: input.categories.map((id) => ({ id })), - }, - attributes: { - create: input.attributes.map((id) => ({ - attributeId: id, - })), - }, - }, - select: serviceSelect, - }) - - const serviceSuggestion = await tx.serviceSuggestion.create({ - data: { - notes: input.notes, - type: ServiceSuggestionType.CREATE_SERVICE, - status: ServiceSuggestionStatus.PENDING, - userId: context.locals.user.id, - serviceId: service.id, - }, - select: { - id: true, - }, - }) - - return { - hasDuplicates: false, - possibleDuplicates: [], - extraNotes: undefined, - serviceSuggestion, - service, - } as const - }) - - return { - hasDuplicates: false, - possibleDuplicates: [], - extraNotes: undefined, - serviceSuggestion, - service, - } as const - }, - }), - message: defineProtectedAction({ - accept: 'form', - permissions: 'user', - input: z.object({ - suggestionId: z.coerce.number().int().positive(), - content: z.string().min(1).max(SUGGESTION_MESSAGE_CONTENT_MAX_LENGTH), - }), - handler: async (input, context) => { - // --- Rate Limit Check Start --- (Admins are exempt) - if (!context.locals.user.admin) { - const windowStart = new Date(Date.now() - SUGGESTION_MESSAGE_RATE_LIMIT_WINDOW_MINUTES * 60 * 1000) - const recentMessages = await prisma.serviceSuggestionMessage.findMany({ - where: { - userId: context.locals.user.id, - createdAt: { - gte: windowStart, - }, - }, - select: { - id: true, - createdAt: true, - }, - orderBy: { createdAt: 'asc' }, // Get the oldest first to calculate wait time - }) - - if (recentMessages.length >= MAX_SUGGESTION_MESSAGES_PER_WINDOW) { - const oldestMessageInWindow = recentMessages[0] - if (!oldestMessageInWindow) { - console.error( - 'Error determining oldest message for rate limit, but length check passed. User:', - context.locals.user.id - ) - throw new ActionError({ - code: 'INTERNAL_SERVER_ERROR', - message: 'Could not determine rate limit window. Please try again.', - }) - } - const timeToWait = formatDistanceStrict(oldestMessageInWindow.createdAt, windowStart) - console.warn( - `Suggestion message rate limit exceeded for user ${context.locals.user.id.toLocaleString()}` - ) - throw new ActionError({ - code: 'TOO_MANY_REQUESTS', - message: `Rate limit exceeded. Please wait ${timeToWait} before sending another message.`, - }) - } - } - // --- Rate Limit Check End --- - - const suggestion = await prisma.serviceSuggestion.findUnique({ - select: { - id: true, - userId: true, - }, - where: { id: input.suggestionId }, - }) - - if (!suggestion) { - throw new ActionError({ - message: 'Suggestion not found', - code: 'BAD_REQUEST', - }) - } - - if (suggestion.userId !== context.locals.user.id) { - throw new ActionError({ - message: 'Not authorized to send messages', - code: 'UNAUTHORIZED', - }) - } - - await prisma.serviceSuggestionMessage.create({ - data: { - content: input.content, - suggestionId: suggestion.id, - userId: context.locals.user.id, - }, - }) - }, - }), -} diff --git a/web/src/assets/fallback-service-image.jpg b/web/src/assets/fallback-service-image.jpg deleted file mode 100644 index 23fa6af85cef7c4ff6ca1c5919934be638620090..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 387637 zcmeFYbyQqU_b%92kOWAuB)B^S2@q%;LPO(?1$PM2jk_fTcPE74G!oppaT46!8wu|2 z5W?_&-<|dQX4akg=gu8jYj&+u{Z#GUr=F@)t7=!(IS;cBzX8v|atd+)G&D4T{9^z- ztfI{-$jBJ0K~&`wl-@ro006XJz@uRD002;!t1|>7P5VJtj~4qk0PQjSOH9pO9RH2} zyYZvd<@CR8C%FE*7XRm&xE7W!=8q=Fj~Ao!W8sgSNFFhX^}jLeKiKTwSo9z4;p*u6 zXruNIcGiT*JYtJS%xe7~*z7;Bxuf$x_EC>EqELIcf6Dqt{;AzFOPH3%<1fzRMG0^L zKmZ_s^gs1~Oh1Z4E&%ZE0sz2>`>#B+Gynh@3;?{E|F1m8Yyf~T2mnBg{8!$8wTYvt zv+2KXhxr(zTUh}Br-c9jzAgYjJPH8d>Hkaj82uk`qkU9SKJs#S4Aua9fF*zypa6gY z%mLhwh!4O6-~|Xipa3%eJm>!*`R6A8LpS*6&ksKVFL2QJ(Kj&AUI5Tvpkcf~d*}qv zJT^BvIy%NbJ?_5=4lXtx))RCLOthzu2KLVZXc!oeDGaP|TXBStugv6wr>YBPSCIJ~p z)%cW#rdi6b73L2X2-){va#xQ7Pu5t1^Ex^oOQU||@h>+2N#_YR1|}9d8s4Mc?!~{@ zU_MeHc!iGhzhlEoEJZ@E{tp*i(pUUK(%NpHXBdxUUSPZcNC5r@oWt3` zkfi@V`u{*1a9o-BewHwqa0F%)*HaBo&fXEu3*6LS#v(Y*oDOL0HO%)VK2H1#{a>8a z+3fU+F1c*8`F~$mIbAot`|(%d0l@xe!3TsQ{dapX>=>n5d~5oOsw_1O26{9U(ThB_ z`@mSndd;@Cne(C5G-dv{i81|R@n@L26gS6L785ibm->Bt)3i+&%ZUYND`h`NA%`I0 z3^hIVN?i7<{+&7IQUiA}CrwHEVL{p#H|@spdk01Bo(tmu`SYdD*fGwVfb+?B5{w$s zXtWF7fuQXI(bg{iKRuc3ZKgfvxB*+nS%QbLe-|GB%{70&{rNLyh;%~+r`XyA$317JE!a5Ij20`~!Mof0si zIz&1eyGV04L9?MT^LeSy2cAyXB-|+|nx@@_b&GQ@{fW>KfEvg6w7+?GPT}`0X8r-t zZJ==3t1tmfv--ex{$zuiq206zKjsT<2$H zkkI%b)6RDGYx(EA+P|K4>eFyM4}Ab^7bTzR+0h*Cwq-s5)@<>87jCb8EMPPb05a7z zj;l?>9Q_q+^M^tGZgX936(pi6 zmeYLNvr^X;TJAGC-~80sdMq#Czm-=wG*Z!~sS=>ba*}*^#(k}MU&L@L`sSy`X6^Kw z#i;PWZ(S(VBVJD#}cV*wy7Z0 z6+b9+v3$^`ezw@8GyiUDX4uumLWSgvCBQXt3K;zWSoL9#_WZOTGM#pt;g?t-$TLm7 z`U{aanx%%j<#^6z|K&I9DaPDx31Grsz)^lSqoK6=w~h}uK#a9l+CzsOWEoiDOS)7> zp&%}A^{um=OUdd=dQ&>+ub+CtyoU}ww&mfYejzKSvSQF{wd9gAKr;2OTc$6nQR2?y z@9D^`oz?%Q2SxpnJC-RG<()S%5+Fc}CRw!XT-J$5D35g zyW5r!(2tB+n-`xgrux^QbQ~!9i-wgx{mbkZp1oLzbHB#D=IClRK0xSO+J7zKRfM4q5>UP|f819WR(OTWJ@F{Z#AlsEI|o%z7&DIIJnYm4@7Blf zd~dX`)OIt&>3a7z0{o`uBUFC!_+`pY;xzgu;8WBsY|by*Y)IfKRvBz05gfkF4YtR0 zX;Yto?y=dhn73By2|T69N*$;h?pad-mpc(nC}@<%M5t_D^9~jx z4N;G(NSl6uwZqe*&$vX6WT|v77~mJ7^!tSg{TLB6-&Nmp5{YC< zgiN0Ouy<}6!?If3g)v*xhgs;*{e(m0rAkt>x%Eo)4sTAnJ@_UM%k z=7Olr#VxmD$@xH|+0m^Iq;I3^_41 zV^ac0?dmX(e-UA5E8 z#feK6x`#|ZCqzGU7q-iAXx}wz&8^?lmbY|oVpOCZlA=EeT*gZHGk+C#6SE&|J3eqzIX)QN~sq?rCRO<_i6Ff!G;%02I9b?V{jWH}NOrfWg=~XqhV(>z2Zd z;^aoil#R+TiUo!G^gPAesf{-N_j-K$0An8fqzS% zC4x(G<&LZ7r?rB;+>AhVmV$2FM2^0Q!gg5ci(Tl}#_W-^p9L7B)`yoqi17NuEUu`#0}GHm7G7(CL>}oP9Q@+Y7Szi@V-i_lUDE) zg;ujyy?Mvs-|2BGW{0_Q#54X3HkQO+ESTq*#{CR&@RU$+CYTPI&sUa8wOXzPbH@TE zE_f8{mF!)UeXH~7Cn@H$CnGa%Dg)%GGB12WU$qw!?K$(BLVv;J*G^Jh==(@j8B{qCRKPS9umUA5s)*3699O9l_;4emynp~$IKKS-bd+>7d+22xf_>5=;LyQ>I}t+VJx+x+LbTwr_;a?| zgcy}wcfzxZhQ&9`!i*l6z5;HG%LqF{JK0mvF^d2$K{Xev1aO0zxvTp3-YUisXkVNl zxngc*f-AA$jcuMY!=F5;q27jSc$IxKK%7h8u-jYpXZ8_ znNd|TVF&DU!2xqMm{lF_ms_cnD8+pV7m4hsOD7D+=BfOlb%2Yu6%>*JujfZ6DN{eVGr7&u4nl%GG78-Iqk?a_ck}H z-BlWM9AL<^3(5{gQ0lnPE-=BGb5`+|mkA+$ZZ9tA*J2j=2BHS_B=U4KbhfM-0vkYl zBd$4sb6WS)BDNBw0p#MlAHc4;&HN!geclSt2-%tJhumw{%-2 z-OipBaW7j{a6v}%CJzA@%v8~Nnzt3zEdF}Azs3Nl=KLK`X51+~FkAGRt_zSi)aXUn zqDR=WvxMNz+AXjt;LA!2UBs`up|~zVvrbP#t2v@_*pZ~#1N<2wAZ;x?cmN2$JRzT5 ze%o5%2YGW-{s5>it2;Bb8<}!`06aRpcR$LgMSv^;oKJ(}hiOWqKCD%y$gZcnU@V2# zVGA9$Jjb7wLw(tR6oxMGJ^&ch4{;{b4h^UrE5;@%HGH#6NzRR6 zJF%v|-Q5^Mm5jU;BMBwzmD^KL9T8*jOCU4@-Z!4yMEEK`08l-5-`jfF75^;hY20D^ zKlBIh}{+QsFm`PNn*K3QNVL zgg76VhbhCLAJ2~l_c0fn8FIlJ6k-)6kwJ4N9ie(B@v+!3yoxM{Qz5o`JeknLq52cU zPAho^{(?#UJSEZUr&=ZxZy7`M1QPMAm5PjB;xtuY1ocTc7>UtbnHAHbAviNN5lq9$*ts))vJ!1_Z0Sn-y5F)>1S`2n<_>4eX6GXMcy&S0FHzXyq?Jp+OEm)#KRf3`^C z@9W-pe)v73R-fH(xkko1QF_#+%9ojr+uI977;G1Rw8741$Pu#x?_M^T>nVP;I&gFs z-CP1?YT;jQL3pT7?GBY^448>gph5p&D zXP3H6Un=i(bBwu&S(eOO@VL4e!=c24)(o|`WTzr;0*Hw6&o3wS8=UUG$Rmf$M6uQG z#*CrKr@a>j`nF)wBi|&cB4dvmmRl(EjAJa(!k)8nC|%JWy2`d!cT&pK6ovN>+KZj> ziV>ygTQL2)|;!6(^X3ib?XL&fM1b=GOg9L9#{&ch$xilu3wAi{ z6YVlNXlrF8AOA45%IVuU=iSCIVMf2ow4l~967yxy02?|2kviMc9Qf>L*S(RdmA#H! z)JlEx(&~=c?ieqZr2J_DrBgu|L9%@k8-oKcGWACxH>g|$TY@m&7P&IS?{3&qKoLiD zOfRi354p{Q_J0;rK}{uEeg_?Qx%{-$g?=FUIJ3oNF9*|;jWK1zr{i!bF%_>KSGhab z7kBg0zRWPgWTw#p?svsZ4D77B;WKe_6si(^789!jFtR=MyFdu*>kk*>-bawsk;GOe zU}-h?P`DeU>QR=xyDNc;`DwD*vZ@-j5J}#VUh3ex?HylWufMGr8;y<9Vt3+bmw0vi z`G=5wtcn|-Ve0E7_^Zn2MLAr7yOV$;D!lE$q}%4^`$Q!8B1`6^6iijY(m9{y{t$CM>W!MaG4Nd%Q9rjI($#A+1jp zY6>~6?E*zSF3&h(8|Qy7m7SJ(Fl*40OeYP7=o}kBjcg=H-OUu^pD6OJ*6YQ!cSOI- z+)>+q??J1YY@bH$%c410G>on5CPormSVe%=4 zMXwV&SjLUq#?bJ3Y$~eK)OzA@P9SFMn}YMnlleVUs8|D52OpHvh;OGwJu(RN!(&BY~Iz5yC31jhx1n49B*N&e4{S{(GtGbB6uW z2Un(Kii2_%y>IB)z1b;a;>hD>DfbkPFEFN<>M+oHF082o)GFmx=Ahc1Wz0(Sd&>2v z9a4B~&?&l(y|*=EEUovpCbluDtMgkCy;EWWwok+?DmyIsxi&8K-L^33;H-kTqdQF9 zDHU4S%N_#G%BAm6Ftkc1+z;4;Sd6R~m%Gndor@L3zzW!~MS>}VrwY>3Ul#Eos@(~o zzoe0dV}|bn_*MKjfuy<+as1I(e`=nsH0;86cz2{Jc_RH3Ro|V2K*1S?D-VDUmZU<| z9Wm13j~&nfa>iU$XC~h)&dGj+?Xr@kneDpV#~9IH7dDOcADeSJ*zs#AzU5==J$@kz zdlr)<@=;rw?-FokZ*IfO@2j^2#{|-YTQ)$cR0cyLS-yz}G`1~@0-I|l^}Mci@&>w9 z$%6yi+xe_iSJ=JhMboXg@9g!G>THHei*4D}H&h|H3KAIeb~gClQ?j4plLqH#(N54R z&&nn7NE%63bpgby%-w=9kkkF-0Pmq8$(JN)<2%n z(5`XZdoevY9>c;FqWFWzt?D(3A9Ayx6M#>q#4OkrJ+Sv2JIgzb;^)rtiJCb9LUBW?m{xcW#%R| zukB(v#m?c%A7BJ@vH--Yik`>=U|v!7g|>H3AJ5NqIEVS`hUVbs7*XvFB4667KAPm; zH~v^O8vRoP=8r6Y$&A&$e^6zKmyS++x(2MC%osh_Ua*urJzCK{yTMJp)()^5bh`GK z&X)@cx7mCqABW=3%NDPuhL8bo@->eqhSVypXPciTr>S!t49i2z!ATQ@46bl}uEOb1 z2uNsxQJX2%x=B*f*{ysH7rW2-cVEnxQ?|`GLR4vA+Gsv&foN|mQ`M z66Dv@ScwBYEoj71h7SNyfz@}x-g~O=1#=URJZ9Y}qVJ}>c4zvYcfK9q&4@_1^I#wh z7*YRYoo+PDMMX9rq1wEhb4F^~OzEyYd+woO&|r;hVm5!xX2Vh4#!Wn&U6|!JYbw>5 zSbv7{Yth%Vo*{U1__~!ftbU_)#oX<^g(@@+KS%QPpe6?W3o_MM?6T-{rw8>*Ww~Et zPl*wRygfR8G@o(;v3%% zL*@t@QwAdSLfIiPFiYZ5v=&&y!iy#%%3_7VgP5=TBF9i-Q#KW{QO)OZBnptQS+s?3 zaWgdWqvx!|%q$W$qD57ieq8ihFvtRzAUuePO$_iF7LxNm1Hija@DJl;nc$6)pzY zS(pL6s{Siq{ZuQlTPYyLdF*A6UP6+l5sh zfstUZz^k}PIb-IMtpF(IIK26#DvfhNoetD_o1te+Kighjbr`_Oz{`*p`ps2n4WzhG z*uHF*3DaoHyl8Nnk%@^`sOb?ZLdK?4t`2NQ-4TYg(DN}m7uD1&s7kQm?bR1M>DlIO zxHYAiM$BcM+m%b%AmtwbRjI-ulsj9$olG78THa3fNSmSS%7Yq9p*ul_`qhB%N%2OL zoX+>ocHN&p)+<^6trpcW1f9htNL(Gyls|!n88R|+H8IsFC$t52+MF!9>1Q<3J!QNA zdg&}glI}8C?r1lR%~LJ7^A|~?Dzw;Wbrzb<=>y07EmJl~lkT>R!_J`5A@`d3pG16= z-6Kxz+W);oa1x@pNEt<>HyYBSrf&mZ%QkTF6l&&GLq64^=c>k$E<`(I@NlWq%g3es z(g;pT`7KGMpe|1f04C12;JBA0|KjF;_8Kxi4*ss`uDvY{a?btajxplBj&ee#%an0( z3$9AX)jrQJW+{TmZ%bXgFnrG>uMYkUPLPMl&jyM-m)d{tsvV4|6I=z81w!(R?DOLn zp8JrWRrKGKfA$>TCwKq^L?#)Y^b%K$hV#NyX;bGI8DYUNz;^sD3T~lRJs7eG^Pa%l zVHRtYoZBu^!!L!YmI^Kp-rP*-5+X-G zF+ar#;sVh2A7{8((wM{s#w#&pGm8WT7H0*H`st$hFwwX_SpNtB90{yl8VV4=>R$v`_4 zfj#gGPV%GAwN~+4yd&HVevaKF!; zrj9Gmm;k^iX3Y46F_<{2GY)|lvr4i+k;@zLZjv-w2;sZ$2H$HqL& zkpenb$@CsNN;1D}35AhoZBqi8zGVwlLw7K8{#j`EDaxpBU z+xFKQM?gt`@qBpoSFsRr*D}p>rCVM_XQz8Sn$aiS?suwiy=%4e5?jy9`HJC4e(6O<(TIxxwd!cDxaM9%8lV!}c-ZbFq^{(wM6m%kgbM_mz$ zKOzg4EOMRoI4xQ%@|Cl^vd?uLWWEsjW#Sd^{lG~x|Lsq9?LM$hW0Uje1~CBX>vx^| z4}kGQTHcSdnMpO#Yr2>$xJl5FZldnTo!(@`>ZDws0+r-W+wA-;LuZJ7!BwckYwyEU zeY+j%=SiqmTskHoTmkH{3o@^R9a3H+hHGPPFQu}Cy<7Ac^PgZ&8*{>*Yc!H1eezJJ zl0rQIr~*72_opTaUhcc2+LtmCZoN^eE?7xmecvMw{bF9VT(K5EL)%_g{fH%?#~0n@4CNjQh;<}+-*&iOvo4HXM5ljQLi+Zn zcz=bE>y@x&UE?8BoFK}ET|csJ08A7o#14rx0X&|s#1->IrPAmn1Q|hpKw|p9memR@ zl^9vY#r{!kWNtRl=D^Xq$MLzq*Nsi~5`Znlc!Os6zI%Fn8danLqkGRG{R^CsGYXfy zz@*{vEtvx+)WSciK{U}5@-u#67tzK9k@u?A)+d$Wfk(4Rk{JV&880wjjD9&FT`|_W z_`ks;;y}(;KAT~Vu{6K#+B|tJMskj$(rfRA`Vogi<*gI$*vBbYC?mvC`${D@zFFw%5ap=3`f_ zOk64ir@Xy>_h__dzHjInmP&N=1bW^MxV+ZUwpetU6kZJ&+sfnx@qb^`*ItLB+Ehh~ zROme8CPml8Q+;Cjt|Aae(RHP%^eHzPn>XV~hE$|~X8+2*fj;U)%p)yF#grzVQVyL0 z--5f{04S+Z(ef&&A97bV9s}mFPLq?S?CK;@zUa!BLlU-X>&$2nF#! z&XkZkMylI5_sf|)*Ok*p#}B-=o9i6VMB07@YdWM_brH*wl}bm%Ns;!LW3j6Bz5SlN)oK*mFlO zHBE;u(RVnEIP=;{ZL!Mj>V+TltE!1!RR9I2eeh=#%S23?g(Kfz5_Leh6~hWF6LxG` zrmKA$v=&?!?!$b_{c_H7ZK--xs_`)zHnbxx`@Y&_vCzb`(?NRzHIE(NBqjsSNC~-+ zHy-|3j)NW7g9e$XqA@OF%lG=bF;jg7Mo*=Ma9B4sH^t8hZBQ-pEnL5EIitj>&8?@x z1Sz6sRr)lwVS_caT=>aM)q#QKme77|!@lCCi1#a@qCZWCyr({`HDLXnTz!FixGK8m zJI7Uw_zVR@(%q0Gcv8DKR2))9)KlMm=>0{QEj)m3jbpbgnK2NCz0ZVgCAFwR+Y$iV4dRe{|#1^t3kzz><$%9qqva@uZhz_)j#xjdxrUvc_^d(g4$Lad&OB=+T3>(OO9+;3V1SEBsnW8_aV^D`he_w?GnyjUlxn4Ym5~kJ4q}P8P1N<}-bzDm!uMlG88cf92+g zntC$r#&rf?eOR=00<$=({QgsyWdp)bGpe4qSmG;bMwuW9WGV782|1u8?J7(3DnA(+ z2?G)SVaJi_P8epXenS;V4r!BDuJTZqdSblTa!&5t?&+MX|2vPK=`99O;`y&J%Fp8_vLeAur8rumvL~^%obUY1LUV zmUnNd=eor}o1v+mS_0ZSJAmk~kL}U@D zsO@Fw=!)nMiu0Yloh`$!mmGr)+|tp|uYV9R!ysrf+vBNnHDJN-+o5dN>FWtfWJir^ zqCjFzm0nC;^W0H&+>^CzmWsHg970k9N7P&K5m>#6_T>e=Y<1kZlHZfBrUhj;BvkB6)!F0e1*zoWn}VcIi@tiX)l1jEQQGm2TxYK-3u2A zWCoZjo?1x=h1@Snqf^gSR`nM`q!u!bGI~)tMG_`M7c#Zv4*=^%#Ps`o9!G_fL*f=T zBotr8Y^m}t+}M|lEEw*?F_3*mrR*?}{Do+xdQpUY#(v1cf_EXBrX9ju&Yu8L3ycfQ zgd}8Kp>iZ(s*5L46hVs0-RLvclft^1pxpPIWiPSmo4A8|ct34Ns>wbAUb+(SjbUFXwH?0htX}%>Y@wJbGYp8Xl79rdoI+R{IlsIBJj^z2c z8XOarEh#24^ZE0(g;Ebq-{Fr|P^4hzddl3V!qLDPA3UDf%tdu0*ugL8ZP%1E)l$-Ei!CKx>PVy9UuEa7=FNeFgC9Z5-Q zwmfO^MTZQxw?+}c?@?c$!GiHRMmcK!h>Z^*$@qp{yTetWlJ&{!f~P&loFfa#y-s$v zXSQFH(8+@KpC98<>(jh3RyA}x@8FCr(6xo>;)f|K%7b;Dm}6!}u>8PPpn)|u3VeS* z*1RI*TSGxSK2DU3vhIfTvO&U`szGU}eIJgeTXTO6f3Xmf@ie*3s2>SVHluc922x+E zdO$1Vv=%XwVO}wJ_?M@pdlcRK^B2Xb=xIDBANl%8<_(;9zkx&+fCT;~BGAj$>4DBF zPM`20lSY!k&mkzQl<9FpC!Sfm3|LXn_4l7>69*S4{|t>FYT#)M!|6`_!I{dNcR?Sa zFP{x0S6wTUgruy-dhRe-1bK&6dhg9nw^Emh7+S`4$HDRE577cK_8C@Ss{dgfIgX*GvX zOt6|b{h-U$ZcwiVS0bVaM6Fb&pd_We(1jH+TqbXBOB`$wC9`J(q=8d*m2!0wwlM>$ zy_Q+{s>Q7^OV%@L7_|Fdv{hu;d$H_#7{2zdD`hCEtGcgR)=V^w6U4<0`NpxSH>oKd zfuzVl&Y~R@_vjJiZbmd7ugQY(476HXf~<9~ zputT1a^)0}C0mTsxPfsz0~|E`BBET6Wzz+>;+7}U@b*wP1y;?kRqJe+_G*P`GQo^) zv-u@vLO`hAi_4BT&7*Wug$DMlZkTko@(jO$4(fp#^pADH!g@mE6_@RShhLwMg|UgF za`#7|U4GE%W2*MPv%_>Z_u-*F63B#`(h`Q9oa+_SB}o$%aU@{bB!;i^q-=a4D7pbo zmt355)Id>+A9DB4JhjUoy__#;I2Nfi106aT;eA%DK331@@=gT5U4eR(qk1OIh{>(s zj8h9$U8-z+5ZPP^v$M6N9iS-6hYkN()WkVG!=5wBEk#<$yu{Ln7vLVSR%id3#iB|@ zEQU!~wZPX>HxE{Ko0KL|$@4a*(!#Uo?k-E`n8YU$TX5P2az#^v;BIP3<5W5g+ee3} z-0+z?D zLOwNJKTl?>VcI)9a|vPc;54Uo5~j`)|6s2^K*aYT8xqECyxAXQhXW ziSW*vovW{iZbmv;xC0hF5Xw|;UTO4;_Gn^8n?J~Di(GN`QjC36O=tVq=Kd&gi2;a%{$e^3y<+R=MlL3Zmp(SjC*Z^nt-uCi$Zm~wr%|6r+ zVz0(dWbse~X7DMg!z6U{3cbqUi(r?NmS9xi)syK$_u~n+(K+qk@s(m%7~TljGavRN zUukm>j#fw5E8ilEkUZrhbFQJx!&^+aXenPr##i_1tTB&^ybElJ|2cSvInu~zEl#RBe2Ff&6y&K(kmysl4p~Br&64| zvH_Osh3XSe>^toLNSIM!c8T^ISY6DCgtT)Xm=h;T-w#z5rDE^ zAAVh`-J+QMZcg~Vr=6yD;JG5ZqJzMS2JRx`OQCHst@Rbb7Eq6Kudvt%Y17iw%W1A5 zlU@S!+BZdx`yRMw3@x7CZdOyJs_zh1I;GrK@$4^4b+ah@39z^x58EfI78p=lT3@VI zJGAOSVHs=_3uYIkWH=Na!uIY%V-0AZbmhh~Kj<}eUf)KKr0qe5-D6hChi?X*^=qFo zud(?1@c%e}k6fjBemLq8gU_ z!G>La1IW^*(q6z>RG&p5B0)AMsDuxk;s|%@;{-Ep|4+6(*0D7U6|+o0T816-rwGeY4a5) zPY}-|K20lwKPFIWl$jQucGmd~c#cZl3OaaEE`KY^I)jqK*A+xNpg3l1+Qhek&7Kno@y{ zD=xl2|EPW+B(qdWWi*QW&A}QmIKd@Tzo}Y%XXiX&+aMp`8JOJuRp0*# zTR#QMlourjCzo`}-F-0_^K|L37TLHclaGxZXd>oTdcxB5TK&5#ft6;+ST)!SB@2(&(zesyv=rrKtYmw+UR6tEZ zS>tNOVE1mT(Qvti<<~QgmMz8L;UhQ>wwzC4>S9Tg6Z!&uweH+fX16;*xqa%IcXjIq}&;x;9nSE3bmy&R) zwwS6>NniWt%&7()wwLN`(8Fa%eLp&O8pVG33qtliu`Z@@^YCAX@4j|?YlBib2g$8m zD}2~I5l#n2Hyu?8OvHCbZ~7EXq!&DkLW@cYi&0h*%=W}Ju2^+Jc&PfrObD+uXT}$8 zyDkD`s(p!tl$&H>!#RSp`}HCF;|8)?K#Dd{-eCn1BvrV@cd2Yo#`2m9g*W+B<$dg} zwjVwo-=ZG4ur^ub`4N4yN;+dgMy%ftzO8T8z7s8wYReD5(uzkZ^a`3+HJcJ~G*$?& z!6)w2Kf>jfFMY1&ndj?MCR+*gJydGvhdiev>6~DNOcD5V)TBjldYOK3vy^oK%F|D) z!OBLPqik1BHEB3F&;Xt2i>+rEcvq0Ic3x8mOME!}J|d3)TmyGi0J`$;$JNZiBx8iDP%o6RL#<>!6#bnz?||EHgm83F%-U(Pu3abK&z$h@J&VQ-k6G5B}v>QkI;-_|bJcNX@@BJGg3p@LyuF?y1OMu+$z8$Q|3O49z8d&8LwL1}k< zFID;FMT$+MbI8N-#D#@{-f}XoP%h;w*$~oIg^P>}Fc^7xfyyaO`yt%Q(B=PSj?UsX zoI;6jyqIWHU+IdgX3p4yw3BaD7mpzgS_W`H++9#NYrLpjs`XH3>8)KOv_zPs%vpVi zV(M`c9br-gr&a=)q(HwJ3TtqFIYos^*6D4r_k>D(H(%ibMZc9*>MhSNJ7?>B>5gqt z#=cx3jjx$SJl{}pQ_f{F>dQ|(z@+QcFh&a+;>y^ZVW#>z^Y#lb0X8MBy*1%7nT++pVTYqCWs zdQ|c{x4ie)uOB>A9af83N{2q}+ zmoMbK*GX7oGok{y670RA3rocdfk5)jmDu2g5uGluAV&3`} zhav$M-cz=XUrTQx8g}0ni;JI$XG|v`zTx3v5)m5ntJN@!@8~I(cv_HgCm2L0jL0^` z{n5IX9f)a&wCV1}rQ^`)n95_tUSIQ?cL`1vOH!9mI#CK^XWsm&G-D2bNCO#!9?0BeP9R?;1qy1 zj_5Ae&(^Pxn3$9b>y+gR(X6eU*T++jeU{(_f+#RfAI&H#S5I}pl5XJ(6ibo}?cNhd(P%`ipakH!NaNVdXQ>j7X)!0@k> zWJRjdSqRgmn@Yzq>O|XhExo69ex2>5RzTjOBo`c$BM;67qzN8x5;pwY?U`YABdg30; z!~X_53B-MLz_}zLTVJ0~BZ2cd72}){p90Dl%-uq{q`?*>=iZ zQp$QxpwC=}deqN|6c1g&qr!eW*w7~G3#vK|Stxm4C)1pi{fFalwD;)~Y{y&+{eBbG zl#;X0V>FPJ(IworAo3x_(K}`(o>^TZD6j2HN~&!o#dKUo-s?`DI+Qw zj-|#@DZ>Hc<0b2nV!=VNa=p**qN;DMl-rHu7xkSBohq;Hfz!?H*H%u^YK^)CAm(3D z-C#dLR+wT{o&s%CqkckCFb7QYX!KxMtgd5oL1*sxfTO2k-n#HI?e!*jIeW>T4CrdH za6qpY|3XE49#7FXx)~{idC=GPqiGtSGTmq3(0#vx@#{3uRwrMo!GKAmC{grzVW2() ziH8Rv$_SIcXq+jG+9vH`7j<{p4iFjbWkZlE^^(Ndu>p_W?Q|dWIz*#<2KW_(d3)44fqQr}zw2m{zKrP^Z74lC=6E zkD#m5vK!Injp($0nO7hmx1Va{Fav?0Les$5Q>xHlQ&RVoy~NvuD81#p)HKh#nRWfI zEdo#*^^71Sx*r>XM|u*p0dL#?4cFO9I|8u8Uef%1^QjQ?L4U$NY5xZ|LO5eegqI-R z@<6~r+4py>}+(F#$E-Xwx6Qb@pxyy9& zN3QX6U(P0ylC5r=b^_^=>I-hX_aO?H-iRWTDDE5Q1_QGXT;t_~(n)7@+eO?=@=!}U z{XY!06|Oc*3LZ_1D`uBzsrA!4)eNC42t(;~oA#gz|GVedhRt1m+KQPewmwBIx~Z`n zs#2muBQ^*)kV^8DT1S3U3fsi}GGZl*n5&DZh(F$?CRcJ!eHvb!Ds_Qi#kM3AubXN3 zHGJ$o1#MtHGPPCPFnC{PPq!XNS{+BwkLk8|GV^{W+N09u35#1inT4>h(z`#?gPv^6 z23<_p$p~F_G<`<=&p@c|lT4d~9)ac#n`s)W;j?tbhW+dTK$b5O8`gB6Rimq&w4;Sm zJ31zR`~-;XH6fbFF+|pL2NJU9^s7N-J+sN<$HLgY5a-OoDmE#3xL?!GAdq8)eQE z;5V=ZDYZa7#eeaVq0)x?%^RuXJFOyq561MdFQdqR+@f3#r;6y&lg8WXeaFvP|ACP-cA z=s-?Cs&T@$TUo54eVyraVF$QDB6rQdc?3O7s{IDiEA~qYOK$vH=b~FWHebYc^ezQz z>$RfR1kY2OWy3mBKtEP?M|c=LBuM2C8SDG2f6snQ&mZZSe7XwP;%k>kea*`0??qyBB|3}QO+15xgqPMkmXkII5 zv7lR)j>^VMnOy`;nn>>VC;3^g0|=Q8(IvhZbf18L!?vjo26g;aA7(F7I8e+5H|=^3 zr@9nt%7TI$noU-3wI?(9kfwr)XMzRakv&b`nAS5aT~apOOjG;t=kBzbO_zJx0Wr4I zDK^~b-83v}`&Um7d2PHtO`Q46Im4^RlP19IbBaz=LHQU}O!~fB#4t~oahiq7(tNkI z9CC3pryY@Z$}_;B2dN`43I}U6?9Aft5*WGO0VkhTErfp;Un7|E&75f7?_!PV^e~Xu z58qh8q+=|K9|iccmtDB0+NIwn@Y?xDFTvLimr;nG7JevHeKBY)Toq2&zNP(>U3B?n zHT?Pi!rECkH2J@O8$?7UzI!5>C(QGtIH%P-qmvlG! z`EmboKZEacxUTp0dY{*E9|(dMP*;PTY9iY3=W7pw0lkM)w(&noMdjki@$H0d`H&?k)a6R*Q+=k6-D|k(Mxe=CxN$O3-3zKzQ>V5#dJFvd45INBF3~^bgA0IQibw zKe1LHlT(jWep(Zmq4s|(Zmx%J-e6N)O{2v1M47-c zs3HhC4@J%#uNk&xnER+!9bI0X(hK;|m6Il7KxAvQkKbGNEqYPnT2c4K()Y%qRa)yKwMpWZwSJkB>o)|a>tb21PIB=#27E7T22qLp&$NYu2`^x%prrm;!WKhUj zY!GZqva8)Y>F&pEraI)j@Or^{bNzf+#*VAsR0P{l8oAuk=tp+^|EaY9ttBO8<;0k4 z?2=vP_s1+x=s#KKf^&_@6gF(uW3tXqw2Jf!>y=LQ!?g!G$mLGVJWAjQ6YU>Q*3vAG z##u6!5b3GQ+9B&ox06@*j)qttw($=#;yPAIA%9-$smCJRz7q~t9JPM-b<%~RsQHOx zIwH!fueve$xT5divWdWOQ)`*_!1d*ol2TN~s}%B^GF;k^w%w-8TSn-cJkNR0t7*LT zud&f4DUDEFY#v(0GL!d3&)48Lk|}OZ8zEO%o>zZcd)Ds5OlX8Ll1tZvRLI^Mpp zxuzMkqdA*A+2YlAj*Z+MMs}AZl;V_$RF`37+i7(QrrdzghkT3Wbpxt=ruxxUp&-A@ zmB3af9iIwcbfv#uCpk2%-wP^G6V#a!$1HTsmpcCO$DY)l?{Vj~uJ4FY^#sO}uw8MA zGnz6tvGHBioOMILjc8JlQxk;sC2}$HKdhd>&ryvtNeUiM4*h~=b?jT^8xa%fZrd#= zQW^x3ot+C{=JTpT+p-0>T(2!^e$2_Xy|+&2MrA+c*N(BQuIFA|-TG|2c-hKAqn2*H zOKV?XsrdpeVa(gk?odzeew#I#OgD~~xJ{|q&{-40SkEDC0#8xe!UbVxD9%JB@YcSw zLKNBYK!=Y0v9|?a>LSt&W%|M@R{qoj?Nb?2NqD!DSAA7I+a&0I0fP<}vrg?w4Zn_U-R{`AaI6?zqR&TNFc3B*dy{(aie?har!kS%nZ^yMS{{X0Qp;J*az+xl z=c*p2r;3D4C%6aOH46P^eFdkjKZFO?Y>Ps-W{Z@4$IFZa86nHjkQV+z!tgt)Pp&JD zt?cS<$Evx&HYb4bx>(=`FSOg-xB&-luZQA4rTP)LKGW%cSUK~*nYBMom{wK0e3!vV zF{zfC8frMZm?M}CrS1lu%M>?L$K_f^zyy|4#Z28zM!?8e;kSEt?Jd{`m4CZ^`C6Fs z`K#}8OJAC$_w?|jH$9Ey*{FTpVz?O?6w}@=+H$st&4y#;;EQdzqOAiHkKtB5<%Etz z{p?ECA6SHh8~J51b=1**(7Yr{@$DaXna-Hb7^~buafU~*)w+Fg(R4}pG1$4u?v2^-+t;AB&+6Com}aU=AqjPJsJ1pYAaAyN zaUD53?3nrS^4s+fo1;oJ;@cC3j)Ee3bNcBsx)evek7&64NJYe7P(t9NI)+k??7>E6 zFKqNMS~VXK!5u3%EZrrOClE>&iaok_Bywo{$0fYShWZ(I%*mfI1&i7^$;K5j^Zre$ zBKbfs&UQLH%(0+^pn@bB4cbqVTKOq-}LRc7Q?7Kf)OU!c3dCF1{`rZ5^W$992?w)a)*`I9ZmW=G(o@$Cr z^Y%{p@*kF-oV9&zbcEBg=17)*P#to%MEoTCR!Uqhpg{SKtu27Z@@&Kg@^Eq1<@k*I zaGNE7zA>ksuefj@GhcTE%4W;zdDyeH1Is0QBJe%3{~;K~~O- zWBP^HCK*~%EdN#UyDh1c?{Rrp;(rK^JJXlzo{Mm^L^U~WFXr1lAaZKIYY&a_`Pbf=<&(rZ?}9E4euXq-@0H`TtWsU`qz@2#vI zcWk$P``Y$rP$yi?p->Z0&M4{%Rv8Dd;T8BR#~yuaW}Elp_@$;E{{>t3Q_;%s>08r^0=>ZfNxGyyJ0l7UUabdPnD7ta8Mq_b zAtKlF5CqvOPbpC0^ahPYl5Z1HgZctgd(XY*mHg^tQqgl*#SyMwq^> ztiMukHU$0jTG0(4A02+_Wsi+xKH+{==^{L<2QNJ1{k~o(QpbDVx>(qoX0&-p@?r|* zpTd|z+xgzG=XV(e@<++p`{E{$2M0~ud1d54{7%{WN^_C-HIWsq`%zU%b>QV_ASA$b zLLUcsk*m&Zw~g&UB{{GZ_iV4{X96a8**Mu$Q|k-x+E8r`oU30IzNJ&L7)`fLk*d=7 zHc?r0pfSIHt(iJ${MbBbegVk#$3HH`Cs_6ZrL&cF{&|D}as?K20@e#$loIx0ZbDqa zla$mIprjh{{@CT;qF?X%-G?L|_*}pg%I=YA%@VX-H8} zEJ)xC#|~rASH4OqTzo=radt={c9p!rZa8lz8_J|)-C-@vR#%Uz^kT<&9oYxk$s-|w z-yndxr7*Qx8$J0q2Oce(x=cfcx@m{FWebS6gv%{qdo0bzx{>)A6aNyeuK#k+RB}yO zb$=X4)U+JoPHlB7Zx!iLFou{UBePQ1lkcIZ*8V{2*E+H>Qoq0>hXNSX%Z`y;|Bkky zDsoZ(W<{d-jREOj*6cE@*o^?&qg-Ebv!;Z+;L=0Ctvl3IN@h7y^ZcMbF zlOhyFy2=FY2ZAPN+cT*g#2ZzO{9O)V_E$;_YAzFhtH#ZmKadsZq)$u9_TaVlb&0=% za)z?`7p7r%_O`33QQx@N?LK-r3bw&8fekVcbE7apd@ ze%JeG-UQWb`ijuHD`CA>ac+cd1MGFb1m9(H9ULGQa5#Z;32 zu8UOLbVsl5we3|BgslN|i2@bh}^B3+n&E!Wu!UfPocwN7Xk zo}l>m*C?&u0zZjfS)7OMJHuX~>(aCI*fev!!{025GqSO6vX~J`(uB<%L;7O7e{-@h zjhnhJAJyl5_6NWHu-V&_c&j`F=~UTKwytkcpvR{LN2Rr#Ck`C!PPrA{d{@1CR{eZN3Z5pm~WC;|5IkLLB0%rjseQ7C?S{{>oFiuWJZVCCmX z@~4j6e3LwCyRk%F>J9v~RAztUGa|CcPw~CRD%n<#j2IhZt?Rn1jJga7zI@zBP>8{s zlz7?u{&gKnzIC^f+H>vJtM1l#Y-sFImdT2{2WTQu9v+H3z@EOH4q4YzMBE@IPT$fh zxt_=MZ`+X{Idqae_rEKreeJ#KsIK}?WU$4pXqKuG zlY9?u3vf?3zCW_xS2&@z1@TGD6&o~ey^iUcnlmstJMRs@S$JKYn`5wU23pIn|kPJ?dB zXPz*#KB%~*HHXBD#ALNz_Qb)9Mh8Qw%jAG-we3ca^f0+WELi*=sao0l*XrPxv%gk zB-ZeHQ|n~|>nFPr5L{koUKr)$;+gh`9Z2G@wGcSEe>fGOH*&A&=;a{zDO&Iu48H5T zM!SS-fmftdU&jrVaj5`*V^3!MS3L$_sgo@FYBEVs$xTjHfni#@8Q9p>z+96YJ*qoiqg z?R@wM24@y#K#~lK9-UArD9;u-qv=I(lM(1CjI_m)sfcLcY0wjea=|OTXy*w4-3TWs zPlMg!LWhaDusFFu70l}Cu!m?wg!R*p*m{!5ciDFGF1&Fk$Gz6AZ?Y5j{T)8+JEDH^ zLTes5E1x(v;#=C-2Cxqpmq7FBtG?BM7<0HFK=t>6Uc^-;Hu^!B$I4f%WW76Sou@=N zeqDz(h1M!a>W?RL!%?L1Ww?}$>H32EuZNtc3$6j=4yDf4Hiq0Nnk?Qxc6!za3?LLsAfjTpeJ-C@pn0$rXk1R`?HSPQ)LmcY!eISEK@@b#}#`mwvcA4 zCqX2czCQS@m*awUEybS^jgw3jUCmZg^B5I z4brxsYhJB$4^W(nwypv8RZNVpe?tb5FWAi*{WOKH8o}^*GDspn z5f}e5Cxii_U&nf8xr|O^KQP{2zB`>17w%$Y`P9dQvBH2s>SUGko^L- z=;KV+zvII$vE^t864o5?1bSl8gvtqC-s|1Kf^T*0J!u5xPVfe`nyJ3TK;o!7L}8M~ z+2(6Y$mEJH5-iaujN;S}7NAw2at+l6#j@gxEci`{jmWCgoD^GD;=;B8-I3ol;%YW= zcR6zKA$vDW?k%WpHt2aho4UHs>hDt`AeYifoXE5k=GJ3gk6LI$B2fB*2fJ<=Ux5MZ3_LDf?ar+ZY;h*74>V)-5@8tq~Q^5`@N0F$ll#0d1V6p9NsY!t? zeL%?;&?%Sw5U7{meB`Knsw)9*YqCS9-d_xWxJVqNcReNJKIY4IpHN9Wkz~V3 zkti_@D}j-;K#L8(xlcNrYhAuSYieFFY&1^#a)AyZ&+++~t@NYVjA$sSJ|wyHjkmZ- z=|0>?S1(3(r1n6aTv*m%KmFzCOZoU>GL&7}WATbxYEp?%C+j??1ezubYd9yo+PP2}jA ze~&M(9hE~jw6SGBG0KGY?t7vnJ4?Xzr6;Ed10?CkW;3B5?-{?JbP+HZnrH-ddSr)c zl9}?r*X1kKs;yAtAGYD5Z$@Oehde}_d+G#cxS+_Xk1gm#@`Drv+ha@?JSJSF342_r z$k7AnOf~TWgsOD>kn)`-7BvkE%tZhv5oA1y>S#Jtph~(uf}i$-1qEl?IoDML$udgP zqo>^8Z77+Z0|crv%gb+4mloS&>-t%buUYkW|5cs+Y=!+%5oZ0-j+4&?VAaE@Wl z^jqrG_Pw=1#@Nd%UX@xKynHFKyhsg(Cl7{G;CtfmEB5DJr1+%gJtEP7ws2(nj9>1(u*}IFZ^N^*DC`B*uI% zs;n-fgLa^28!_6>rQ0c1^;^4j?}yyCS-n|Uei*w_fXG@c)BovtX_*75N=nHfEi#q( z!-tv+L&-~pEPhFT%K7;5*tE55_U`VQH~|m;2wGzB=8|oZinZr*{1zj*Cxf*eM3HoC zqTE)EE#f-yIpJe@f1U1<-~6u~TGfp7Y5-QnC1sFC!XYREDb1p{SUt| z$?nkR_}lmt{-a3sDcqP_pAOjYIwDfY^x*HLBT3cUw$z>t!KZ^3GLs|o`I`+x7G7$l z`0Vc9U@1M-mT#zj17B26m~G|nRl_4TBa5jR`UINw?~SnLMEoyw+0&l;?OJ`!E-t0i zl08TR$5Kzqbf46XJHGFsLTg{Mu~JDEFbGMHm6BhY!%@q>MW3bEJtGbOvwxNOBf9Y) zZt=skJbj;$#rd$zuAb*&RFtH;`s-Z7g+=jhK>x>n?(31I<^;oVXoA3ENtVyV9~pDi zgq9$z?5n-NB}ZeN)uY3lbpr{7(orM&`X^KD0q{X?B_HlEG%%AP8-hhMw)Al4Uio5C z58e`5B}nsj(5$bSfim5xR(;5}D}_F6O(AemhrT!LF#t%<;&oAF%=>d#&(G%ejv+v_ zQ5E$(j>L#hMSSR}fN3Q~2tyA6U2;)Q(BY}>+-iwP4_^x{+<@uxE4`+3u3An_#+s?< z#B;4D2GYv;vTjU_SK@e1HZS~SZzLvPdQgVP*&S4*di?lhV=rL9Q{04x$qK|@coALx z>Ps2e;Zf(cqY?aor1H5!+RVvWz7+$crTv|i-e?vtP6JL6mEJhQ6{A8QSuS5rnznri ze_5NX9n+e3=!Uv}Xtc>@GX=VkQ(ig=SDC3KzIZL)tc<#Ri3{gIbe68a6@1zEAC|u* zY2QbCrXgGZQi1Ix!kKaUroAIW-G4i!i49EbqDNj5RB^hkvTSNy1p# z$(_GDmf{e3XYQ_!-Lr$>AVRKKvi1Ba-SLFjr`7Q;|0_0sm8?oed3;KF%bVBmg~AD?{VUM z*1vt=H_;Z~+gWV{Y2zMK#2#0LazeBbHG0yLZ)3D!B_eU}N}0o)iZM2mWxaCANYuas zOsQM3@350%TbO<1P*y>Wlq+DL3h=SD%F-;gpbmjCx22 zgGcK|b$OzK>F2nr0a)3*P4=#vP)^ujaE8@ye<@ntN}hvoyze0b+8gV@ah^oK|F>nCJr&Y;CZI%!bEo$f zEf?jTEw8ZkD{yb!I6y%8P00ta_QAG>w7!e7CutQKXKBoIBT0ZqT{zx5#5Ue$&aGGO zGoxA6uGJyaWMUdSOjA6`8R&^5o*CBJBJz3!FU0iF_S3*IOw=9y!_B>=CXy5sQN>EK zN0O{4jisnK&%{R7wXfpH?p-l(Qn8)4>RKx%!uy-i$Wfr=r9gtiCyYXoP}-+;CdqIT zwVheQ`^&0-&#vOXT-N<{>^oz266^>2l-AlXO2Cy*NvfYy5VlEODpIkaC)-td{iH5P zMw1LrU)KHzX7E8RgcJITGudU)O1HhCP?yk}VLuRSg&Hh7em^mXlaqUV7%rDMu)S$} zcV4(y!Y-R*Urs5RKqId*m$2%U&5~Xx;{RU9zK@^En3pO#t@1%us-NqOCiY-p4cRGN zW(Q~zWBnshC#<$6`M_UpY1c`TemR5T+4-L7$P$ozU|U5jqY@{n6$KEBAJHAJT48Sy z63rKQDv`BpRLQ=P{|P+x#;|q$@*fAC30b_sd zJ^0^}O=KBbM~$;p#OjITCy>1CVcIbY@M~KqX~yk+mAXoUO$D8@1kpt3tG^O5?xw0$ zvDwcwT2v_&*ged@UP|n+#Xw&qnrPs=Oyu+s&ucdi@{NZOQX?GgW;5(W4Rrm?p6A;EFt=@qot(98&TI1eW%{kkyk($NJ-aBL4@5s)Uz$L8gAHkBq_{;zT zcIC{8x;NevUvP8$;F0L-e$U@cU9%3eP6lSagICE9HIwAMiyg#Cu(~j z@ot*2Haa+EdK58`haq&!)a0zfY4HkOaG(z#uogPm)Ac);!vFNqeozev1s9&BsH9Dr z=(9RpgE9Mu%@U}(k}HZ}Gk*!Q53hz~Jq#^e%LdOEWW%v#WF7SA5VpmecbE0yxOYp1 z8SnG2INus@UA4+X9f42(=;_zOl^qI1zi5E#0xs^?wZ&w{znWZXQyzY^xxLCWa;2ty z*^H!+z2iNl@hH}5ttI%y3+$7DieqekBvxoy%1tkNc0eAhc7dLOCpW z;E@u))DXGn__~^*ne-<$&C)j;p1O19|NWMlOB-(*WoUJj+W*0ngbFV~_9%5~+Q+Nv z6mFc=Mz#aX^{B~~zV=lUyW?_gBcWy^y0t289{n_CK9oKFaXx1M*N9O5oWJkueAC+8 zlo1SCTRy;Z6921bkm5@Q=(OjqexF~T9_c%H1NzKCN;lQ%71s-@rb#ItFSDnN^78%Z zU_z62FS4wr=sqE~89$m}^*UA6Xfs_>nrKi0cB33Pj9!z?yEnB9)+sSWNJhFD;8E?~ zcU=7r+O?6mcq-_q)Y-K8)2dV?IUI?F+TdAL!{o&pBgK&ja3O4r*_jHC=BhV4_!ZTU4C2*WCE(Lv`CbrK?1T~0cVN1xE(*8CgP3}WhtkT zRT|bZ-Hn2@4{-~1OdnpNjKEfc6l?pej30kfVNX1HU6)^6vx`UiU|>&6^JO~wGYr~W z>GgC$OPB!k6Dj$li8zP8>F{u5_={eb=<&9?;j+Fww5UZe#FW`$fq|#$e1l?J7%R{( zaotMf+sNMaLN6;}j&ZpnWVYaoV-TcI!MW?~&p9_rF{(MO9E(%{Jdk?sS2lA$N1orw zwIurLs*J75m%qx0$IOlIz0}FAFf&Ce*s}bPkBqMF_6JNiJ9TPURGhx&LjUX@M`!n+ zN6B4v0&i(Xq@UefKt0f1#w;ig`2Y$jO-9rR%6yTy9v^E!rtZnZ>;v`Q>okHt*bFCH zo|~vV?_rYaYqMBX+jYr5N5=aW9d@F_#0XBJtDBrigkqHwSI+^nrN@Xto-B?{ryOlB z!uz^}%{Tskb=rkOXS0PmA=XDTkb!qepZX?K)2Z{SQ(#(C))gaBiT+xr_(jcJ(VGIw z%$k%|*BQWf=GbDxTjrZFNauS6Shh9Sb!(@s2{MMNj;{p+XLivOq ztP;t;4ULq=HYECuw$SVA=Qeg2+96T@o^8V98Q6^^tvE#8O~&|4*n zWKwN{NxJk1Q0wU;FOv&b)(~&qr^UZaqs6P0RxQVrS<6aDPEKhcMQ!a5 zH0{>!!}CRdww0g9{^8P0wu^gKXE05j#Ox{8&m2s{X5FMpE<@7Beo;K6iJP~E-B1)f zH|(3B>n$iQ*I?)ZaD{j;RZWH!EEcsCG%D&lHx2Brak$Z2W2KP(o#ChyK#x_-S9)`k zycCTvaEPfV9Aw*Kvrnwq^@uI}sk@h47c-RXb*Ra#XRlVd!aZi@6U$+XRA?T#ro5%= zMPC1=^uKnc-XYT15h3v+c%g(>%HN#6I!=f}Cne|~lfs*cEp9d@ zb_Qe*F$gy<;XcUlP;uyV_;Sd8!qUI4v3_IZq)2FD$C^4SzR0ySCW4I(Jya|BhFVmz z9c{MLCcaX2YsVsEcMbDp+#Th&PoOV2n7!3vn(Z1?bFP7-GPiSz9iz6MLq;q)6Su1AnlQtzE8i!)5xq>V z8YmE`;q1mpY2M-aS!Ji*#rKuORNi6f)Xc{8qG4oj)SF?~x5Gn0pvlDV&vpNT0$QEW zVsqJejaz#$WvNU;(Och=)f>7(-ae`q$-is7Nqv*d%En0aX0he+rm_m1=g+cx1|GX` z(O9Hy{F8A#7O@$VxvsSp{@~I^^bRS+&x4Q}QY)*SS3_`-RBX$jqEI1W@X{o1> zz`~7VH)Hlcp#Cre%<4o-eVsg#5{&5_ia#LcW+ySuY+Z@rG(PqFkEu)Pk9xO(PIZ_V z_q+a_8DoZaUIzcGP_BCE#^nhsEt9c|?=o571+0_cBayc80dGG1LOzprfeg@^NUj8mCPHvWz;oVP$=BY(|FPI1&HlFYXr*xwv zMg2s#8G4Uos&AtgU-z`P5N0kUW5Wa(Gz$kI>SdZ3noW7SHeqRO+8dEu=$t?G7dT4w z0J=w;0!&CA!?V%%acr6FHs@kI>u7$JecO7LlS^Zt(A`_8^a*(hmfZ%9a!DT2@`btUCl8HqP|5r_8Dln8 z`fjXzIe*>Y6GknXqUhs2mk^OKaHE{(4#4;-XtAyx6)`OMPMGhIdA+RFG5cptCE;a6 zE!cyr!b_1oYMSZ7r6I^m)F-$T*bRq-9FjEjk6e5Mo%skp&DHET0TDbKprO&)rGm@m z>Q&qQwD~eK91dnQmYlF28?iCgI!Bqt7qQjW`xQwvv+ac@9sAyMu<&7m7w5q~SsNOUZZy2jR z?Io_4wS6Ipm{`zUNu$l)$8$6BE@?`UYlTF=l!VKgR>IV zc+vEFsW?S;aD5UTE$+3OjBveJ9_10!ZzfGJD|4JT)Ms{0ZS~D!iLX2=nXbwuXnRqd z{H1;S&B8e^#mgFi6L*F1;lIc=_uSmUxO7Zm z+x?Z*Kyot5!-esAK3gh@xLG`w17Ul`XBQgYkXW`ni%EVeov#cqv@C06?laQrns*t~ z9(?zVa_Q8Ba-81@{QFbvv$<^z8p2eUDlK2XRd%0S{*)W=s`(fkrU@;%fH!@Xx+E4_#P zDk81Wr3m3#bBmWb>TywM*Oy5mb#@V}PW(31aER)yLGwUoW3-;xG=IK**;FSGLsso3 zGe}$DrOeEuc6itCp40A^z3)BTFyC0}>qSD+$#LKCRhODv3i3Lip!`D&b;%j#kv$hdS z=&f{jWmOm@9cxi20EKyqu)_++YrhG#6$j6ZTu1sJF(*MD^qeT!d>!PQtch~8tdmO; zQU{JmjBr)Ux?4=&;|1m*0>aF_kCHd4ZG1QeLUSZFT>%BsPrlFW10>ILwCcVqj6K}X zR!tp>_wuQ~NyyzAM>$TT?S>}~Tn%3kmY%od`AZYmfHVXEG^omQKD@9SU;iC^Ue_>2 zMJ15GUF#3Fdr?ygTPonH|Vu?XoLlcJeVQ>k4CU==i`xF`S ztd;IBw-g#~rl8xHqh=IKXQ{trvqa?{`tdmYC9Y694kpuqx0B;*Esfyraz$>mWB-Vj zT0;l)KP)A~l)xd%?@g7QDhZ23mJC-aT^u(ij{ODHR$?Q>JfuzQwjm(!Vdd+%Qw zj>A)30!L3NbnOi|6cbNPx&Gj@(=z|w8p$=Ss(ei`lSRs%eb(~J*Ur5?(Vx-vxGoZ> zZag4vu|lc)vRl=y6*iP*NpG|PJDb^g*qvgbsRT6;)T-%wBKxH)k?rLnqc84{N1=rE(WpM z$aozkRwaNA5I_FvFX8dL=@HR*hHsnT*mJ~YB0m>}{@pzJJzjxsFNF>4cMLx})*0{J zd6zoMTQl5f{?djbw)S8VFKXe{M(Ie`{!S1ZM6lI4(2rv z#7pc7!8+k0>o0^d-NT88Lk2;bAGU)ws-Av)qk9%MKzA@Fyhn=e^exRAju?}075jd& zEgX|>sN)fjTxiBW$CD7c!_i`V_8S}OtJTbdBlz8waeZbW#vfty=^Pv@Hiy96SS|=+kQBn6}_#{Z^Gj zz`GVosenF?Y=*1#wSVJXiv=Hfgi%&zaU4Og>hG z!tk1Y%Bnm{%$TD2%TDggxEHNTk)bz%Pc(eOKcRaxZamA+M_N^cqTAFvz8L2ti@+ge z`W}sipSl|5qSj)&K;I~2Y30LZv;Rs?r`I}{6cbsZFA3~Yr*m;DHsQ}mYkW4tb%D`8 zp(FLv-~Pi2Reo^#4y8iLHtuEj+FO{L|A@8!E+Q;*MnvnL;$8UFAYkCe{)L7Ai~XXJ zs%OS!H=CtGfG&fUm0}oEQ)>RO_^n$?Uxq{6JBD&Pz zVQrUz68PZtONMkVfaG0=5;O8qvl#{Dj5CMDu3i^2S$|@PAWv)i!Pv|!^y;Y_eQ)J^ zJ;z9vuPCjq+wc>r0-^hiQ24DOqn)lOO$qc=hwz0YhG_nH!pPCSg1S1d#P#=Q^82XR ztNRsV;mbjxSz}v(1xipioeO?F8}P35#x_B7A_q|s(0p}c$8K21#o-}w^g~9ZhAoTz zZ%eMZBO7B+Ka|D#K7w9I&J4ldhP9Ye?> zw1mv2jdOJLg_oAr(rOJEsKiq+K3fmBDKDq_!7i&`TQ{vM; zZ~0-e*`2sK@@KoUBlWvOc-pWbB>~8%8Kf>UwNrECe|4y<9x!sTA?1>(ilWb$=~yC5 zR)|Emrr`&!>zRO`kLLOQYM`HAg5MlT` zxY#&c#yvEAU$tBB6!rgb6|HpD6RiGc<8s%f*jrPmOpksztW^I}g`-tMr&&&-=}A%Ex9d zzV9zl8YR?JRnme~DIS~mH2dHcA@8+rAQ$o_>T|28{lHes=t5ueur;&afL2vW`XC_j zNpe+N>&4-gv8k+(=5K!!@!e%!n}MD)zsdzIdU-G;9;TA6oG>-@KFkxqo#`BW)zZPZ&jBDo0L~^KW;%os#qya&O-b*zinhd@nk{ zcnm6u9W{9!D^vuas{NE3koTo?@6xx+d$d(D=4&ia`z=W=K@#<4Uk-;t;=C1Vbkz$* zJ|)`qPLdbfTj9}dlZQXauWyaZJLj-$SypB#pi;1+-7fEc*;6;FGN|8ITfEX_!li~# z>E&vx@&E+*G71Hf{JdcqgI``<+V%p}jJYZt`QAlfJD$(Csa$Fl6FpSgTWTF#DP8~ZWseY{KUDMf5 zG(9(AaI=h-GS_NwTpOWo%I_^bDj^BE&j1a{QLRAgD%ja0<6Fw~#5)+OqZ zg|C1FJhq5Yp3DDX;VXQ_RnpgVZYS(EjA6RytMA53nWJ=guZcx-n~W!kr{;8A=I0n% zf)O%%DU(i5%=r-;-={AV&l8PxJXRyVr!b`x{o%OyfoQjZy82l&xll0RGZkHp8TMo7*@Z}UX8_P}dZ+n}sYY01 z?4gSGKP=)>=ZZi`&p&(SU&&pByRReAvyp$;UAO>h0JYo~-O4gng49Pa^{?C_i9_en zeHC`Vy6E#L)o!MCDV23}v}hzr2E;P6OIuji|7TgIZH;tytvLk6#2#Z8Ot0A$pHK@) z0f>uyoB2>l{wo??L(Qpw$O_C~0Mw%5^|JC zMB4I?v{9G*t+2oD-jQ;c9RU$EO$Y5~`vG+Nu$K%}D3>vp_~Rd0u2zRB-U~w*E_Dok z&Ey)~U|uIVkET-bVe>K{$-eXREZZ01c$Z26Am7e>GHU1EyM(VkMg$a`r|Cp(RPZ-< z#{@$jwhvWMNO4R5Zl(stV~RQrC9K1_PPdbcHdW}T<5EUb@ynwKq4x6%Kuf^9#9qPC z!OgnNlGe(3>?s5E=gP=EJC>QiJ0tC7o;N$Y?;pe^w7snx!aZfZ{29GYkeBKSyC*Xy zkXT-)4c))V4BAB5wSHOi$j4`>p+ovZ`3NWL!m8CH^hO{+Qb9H%-K@wnY+ z-BN${`68TT3In4R1#K8M1CNjB`F z0Y|523&%9y$feHA$FjyM<_wiX|52V~YxEs?aqK#aliQEvF?E;^MZV_yyv{KAq!9Hi zi+*>uZe1_B187V_KOQJ5^$Isu78sDjUP1OwMDWjgyGNY|DkMO(i|_6@esTJ^SXS-uXpVKI+r{|7+NbETarzQE z_MwSO>sipDy0%qvZGc}_L{@VSFD2W^m0)hk%5btI{6_-Jc#J)r|)<7FzM_x5j_Z z^v^$4LE))PkGt(D8}~m)H`yeJ>0*!qDBiDnL2Fp5{zX{fu3QCkTisBV;Jd1uj&i-0 zp@LtwM!TRZAgo`wzmKYWT|fojLHRX2B=Mdsj`t)2Wu`qWghI?U z8y`LJ!ril?S(2WcoHrncz(kt|@R8F#qfl0Qv2YKj&bT~;aB)U{{F)%--E46Jw4vel zTO65?wyAxA{-v?9q@xcCtD-s`fm1{|ngW?Q(xtByH8}V&C@`Xp=;fX?SYnH5F;SKh z>(6vf;YMZ4|DzDf9BH6Dbe+O?WXiH<%(~3v&^jtD7qg{VRs@nKk_|Or*Cl^D4F)Ud z^5yiF#;;pf<~4gXDmZ#%3Cv^ngT7NPDe0+pU1M-gAFo_11@D$Cz-zX|@g+{4`Up%V zeeontHGO*;yCGTAHn26Qnk%f{l++(!k#59%Ng6h*^R-rx_^lDr68cQm07OZsyiWrn z@l0)eHQf*pJXs^oI)m;_zqAx7YCt;@_-GuAkfp%Vq1K!i9suH2HaxS}j@cXre_~rr zD@pXi?Pb1Cxf_K__HUGR?ZnZ*2=rI_m7kJyfjqGv6{6)QQ%f=f7Zv;{YtQiPYr}4~ zfJy3rpYi+3TOhRuv_lc}L=EkuojzEbX<}}cWSbG9!icehCckd_Mo&V1C1 zK^HiD^*tf|i3~!X_TA3-a2iyj9m#{h9ZIV=uuQ%9%?^g)#{#}=g2)r&+}N~ir@G{Q3)%O^xD#P4LdFH*^?7h)I20_Tc4g zS=6<>&79_)QjwGcK7)s{G+BLXGI}EML_OoL%tjUBo^)-;n3r2gb+gkaeROEpp+V}D zT=dN#UU>xVwcYM!ngT(a(jInN^j*;=>FupPB#c1OW5U-}d1WQLg<-78e~Nf2hx=lU zVp)_Ea}(cM22A;4{`~(~JFBR++UQSHp;&?9#Y=(WP@rglwonQIiUfBrMG`c)w77eq zxCReSg1ft0kP_S-iuD_2E@sXBf3Ejg>#TLwc`x3*fBSh>Es-HKOjqfAf8jh6s60;; zt$m$anMhgQm$QKAr3I1?jj!ZwY{RudK@ukobh<~I( zYl)GG#R3Ss0UGwvo@d$x1|MyubOn6M4~pFiALcq0zkx^bXMf_e+>{nJ|A#f#Z4`5Y z>SnNaXjrhfO^Z`lKI^jMAHw*fXyC7OcTluwUB|%bjq6? zBB$jQo^BNMNB>;QoC-LM{(Ebs@idy!HBNUzOsAWH(DZR&5 zIacDM0r@3|B&DkNeTZI|o_UwWa31rJ)4e|}B5x9I=-6{m$9lB%{TijOC2U)FOYO-O`eGnC9oSB%$5M<;(~#MBf-@-x5ss7NblMUdUKs!M-X~9X zsr$~?KRq13^iJ+)=qZjztRfz?S9;2k*IQs)b@oA=#k0m;caEg~axFCeAgV5Ganqeh zo=n!PCU>?b=sPt>-Zw*9dw?xMHo*Pql5#*Dk$Q!ac`Tdo|oj$4`Azlfh zgBjM*LxKU^wb@HZJtYrZR^k}X9gv$UPGNF-!IY2kT3@8CehP}>Mx=fr5JW;9Ou`_o zprNrC1DG_;`WeU&b^8wlB~lC9iw+t37V`B_8XR{xy&TDv zDco5IofR|nI2Ticd9{f8Q6uU%6YvRe2Th983JPgkEi_sNjD9OiP3htl;x)!So${I} z!sGHhLupgXZ(u7Oy|ytoArB!Qy9J-6_8p2!ag|(aQO|}*X>SV7!RqZfChHrXrd3EA05YnG5EMNyoUfrgMnlreAP~j>^UF5~Bj7e5IS(2)sWzETkJF znq~GO2H-{H0+_d#zN?=eGsU#(s4XsR81T7xHQ<{wVK z+|4zEoo^Q?fbm4;`rJIbvwb~h$TC}IHX|Z;(Z`^){LxrE&C5++Z38>eEA=&$%{577 zOp0jc<3+OZ<&c)>&#mguvVEW7ls-g9MvmR0R7?gd7q1MDIj=(dwv@2i5XD=&KFR-Su*u0#%8&8<*8Cx` zn3X%)*yR-T=h~!dCTHH`D%o591RZcnA0QdLd7Ac0btzf@2vduRh!)NWM(6FOsZ{)j z<--rZ&5O16{S8Y#d2D6J&!}z7UKX-Xq{EvN=smuJk~%51ZYQ6< zsh_{+8CB1lsJN9cfJ2A2GXN=}g8 zkt5$L<|UH`Oi$v^bzYatSTBaan8V_n@6Dgv^v#wLb3Mpa-FDl}nTOpzS6EVWYWPr1TM)zhD8h0Y%2)4h&S-BV%*JRoYGrD8 zZxs7z)*seMgFjwMp@$))HFRQv0S;In$QiD3pTe zoBFqpFCb7P@fgQftrbpcey^~!6g;!Xyf6(`Z6%6ZGO?_#q4$j}x(c(PVp#a%%u-fS z7OioyrDBZQvS~%!QbCbcWX?xyYb8A49ze%`nsyp69>pi12DNd7#ak*a2JF<#X zj3n`^;Hh{!E_^R%$-rbCoT*NdTb`Gfz$Id)w#*4sPnTY7@L#vLvWJ&BrJ+nME%a7# zW8?ROHysYpUHzmP0<70o#ODLv+9zB7dd`qv)&>~s)R6u#s2aGa; zh+7p>Y0U+Ro>d)qRNpm~zx>0T0}yEx0cEM4TnN2A^ydB}kS#lS7Ha$Lk|!c##E3?i zlsSG6);Rj`%;d-weV|cUCjgo8$9Keeb(F+z5yhR|BP*j<8w#z%NuM{z?isP8L9GZqd<0wA_M;!Pmr(z%U8$t z#Dm#AT8bx+Gqq*KgoU{VX{_*$`oOX4`CnUWQ>D|0Sn0k4x8G@NbGQ>`gY&~IX!3cy zo%}Pe@hkyJSU_cq-FirPnasOBmqWs(6Xe~#nTIBSC(m%RPBfM^E7ln+y+lYd%#QX7 zyX#-95kf>F<2&n(Ea}*LQ|8HZ`))UvLDv)X#pN_t>zm@oUG>}i<5l$AG0Zu7VyL%DntEIm*4uvUwByT zG8$7cBTzvN=Y~k-@yoa0rMj%GZI`Di6-WaQbnsO`32l1o(>x}#V8tw&^{)6h09E)T zwPU*>t3(vMmJk!fyvlNOO~K%vgGuTLZm-`NBf5TG3M^;W^`Xa*RwLu}&AX^&%R_yR zvi`%OSx5!uYjbUcHp~|EG7E%f(`s{u;^}T1YkyL^jEy<`D+?ifIiA)EjN z*GAsjI|%s1f@fr?oo`imwU;Z`0ns^G9)*VbY3GP$P|HDg&%?>H#cqtHL|WFj@pS(h z`qahRz9s&URvX#-z@%(+1WM7HPNw7Fb6e_1JGXUd_nc{*^zqv&vzkEq zAa-w`YiT9s!`}qDdzTytU+1-D=mxU|&7T;UraMjc%T|*Sv2uV&>g7TP%YT9&6$Vf@ znlJVuJlws^r;%}tvT;<8Eb&Y%7{{~ZG{0|nR@)uV+JTIe4<^F4JN!=jHK`1W4GZqx zwV&T$<+S!KiKXAHia1{FRY70sn_9V{+?~|?KrrTisdlkgYD-TgY2AEXHGE7Dj&&6# zM_H})k{33I0IaJ2VSQr-+IRZny8MTgch~bT=*W_fhJ~o59Bxci*0P$#H*p|K)>S zjoa^Dz&nZ3)bX&vqr-`G^=dp`BuS-oqUXh)+pg{vb}`+@-6gL`JAC(Vi(6A=-3rNc z&I(W_!k@2lfh@=qJykqN>&=>yLInu*zkP+E!Fj9v=4)SvRC~S~eXX#ab&VNkw!NT}Qi-Zt)?4Mdo9ssG}S zX|j89zeKDkw1$9wAx~CaC%!3#`g!7p;CJ`{_L5OKWR3bdPpw3!{e+M5g zI|4Eu-Ww!#CLA1Fj?B&zQf*M?m8jm!pSA^!dX-xnrov)jwi5M7+1&RawNZ?E!zD;2 zq-5gw>w`}VVeIz>zo(vnsDa@Nfj?x$NlASTK}PK1l{ zLVAk$Q9bY2!%U^(fnbGs7e^+Blgxk1&3mucMUN?HqCCxMX4t*wzjatmAup;GIaRR^ z_GJ73x<@uiLTS2ZhYrJx!_L(74-;p#2%w`4kia?pUaECWQ{^QscJ=N*MeBX6HWR-~f@vlLtCQuVNd_;uN|+rdQKx^+eO# zbuYkj9LC!{OzeyqYeF0hx4%}rFD)>g%t25x>FFlBu&XL@&wNv&nyc!o|9}Vtrb+ zPuR^p|3Zw4u|{M~A2Cvd#0Qa+zKTU${fAXOU&^?a7j&Snj6m2Fl2$6wy@a0mLMms<~-GA;$@WMoe(0=MneXQw)23ViILM?esenoO>Nc z=;-wY)joZp(YxOxMtHf^fz6q8iC|fn>nV)JvF=^ojX<9~F zSyiIIse9A^uw;8lRybter#ZKN;&_hCqJd2SVq#}UCh6bSnHEuV#SQ7QF1OL_m46cP z0dA;my|8K7e<%ARlkCGW zb2rN-m14L33!Wp3hFI;v9E>2-pLHuJ7SZk%gj4855-#=~hwpv!D(3Y`HT5}!awZWW z#qFVjL!(`W1t?soFCy#wI6++SvG4FdESF9BmLIp&n|}Jq|5&CgFvL3_(;lpi|4wKL zp=E9>OxwC&ee8TQZSqIulEV9TlkH@Ig7Dv0)c7&;?IG96jix${X|Whl!shJ#6qQ$f zv%bm)H?hR17;&l@`BP$2Qco{V%gx_)HT=~BRfcgG=|o{D0NZ}ASTaU=Q0U`%B=1<( z`V@cZx-Vx>M&gk%Okr`;=aHIAPWwNpUU6CI`+{#EEB5`}>4aZhh!-Bmo^{~~%nh|Sc;Xm=YheT{hI6JBaW zSN9*5`1n#on7c)GS8{_IqDE}CHc0L_#!MWC%sZ|So4PLww^G59C(T~3;_nI6Wo^}D2tR)b^oM!o zEz@b6aJK!m!r1bU?^hoRr{qcj;#-+4m>>uMI{ysCX4=Nq&Y-ZnU(8gpsAfKmYn>(( zL%D5Z*N=3{VYB9e6rdy@P9@ur@K)rHB(W0VizQx?h`9*nubNpfJb;opv|44#YkhNW zZGY0*_L|6Ds$o6ShKi7caA3%%t`zBd0>)`f)nEBj{^OTSeo%BN+Qjn`K~_kU6Kj1r zF>Ix2Orri4v}Qg6D{`(Ht2L47kwgQpvjfx$fzSkwN%T;CbY1_eI9{{EzooK0PwSWN zWN4*8p4HL)tDcd~LXP~fABQ6k+|45-m5SZlhz5N{J^uh)57~{MHG?Yd2@@8?;YehO z^|vh1&=q6s7%HY9deX9eJX-f~yXR2g)^%a|OcS*9%Yk?lUfCJ?bsJ1S7bV zV)tSIFT8>~qG>vh1{=rX5v_KjuO)N+1mGh!sC2Oh6g3+Ge}<-|$!m-t3p zw+V9N>^by+N6RMd#gLLu=a)T)fAJ4`vie8&4$6OSAUskex{>aAu^IuXXX12tEaoG# zGamq=Dn2D+wdb`W?6ddL!i3%68GTwx6K>q)i!MEaT?rau%Sq2;Qla@YYi%E$`*<`@;Nhjjb5m3u)M z89dW-9%n7rDb$Y(1w9ISlJt*L!Jz2&|FFQWr*t*cvvxjFuV-;&Qd;jugX4pWaN?72 zH{C-#8yEZ3mRwd5@@txL5;#O&vVEF*9v3cqVV0vN1^NY zDKrjY{hdb=R*-BXm6%rDbEMd-TB4o(h3|DZuV{259ik#?vUUS(gFN`2*YEv&j3ntv zpe2?`;eN-SosFdnZsoH)x(#EURdX-M>>B&hNZN;UhVZ#Mu3^>>j@`r!qN~tFS3Cch3b@5JL z0|sIztgH4bvPzNXW0WtqggN_mOGVXzt+SP%44 z6+GzrILNc_G~(p+ZHn4k7>>grgY8JPPuF6>3OGx641g36Tb1XTVO)0i{4@k?osuvg zMcMh75@AfWh0tVSUN>Kv)Rs$mjQ;DU-xKTb=C4yMtep2}@jQ{SaI$J?$zzdB^yNx* zkOJn8u=RD78N7^U;5`%m{Z)J#04K%bAX+(eo2`76Q1e~wkMcZ+zH~jOfhxLRPc_F4 zt3+@;DmO$x`<0KQ2Np^ZK%V11J*SyT$`*eqC0>#igHjlG_XXGBXRsw-b`Ei&32;g5 zjwkf$xK1TXXnI&g|1|6hcanrx{nfYDf4|nsa%C(D2jq|3i0mXGt-hM3sZ141QjS!n zUs~ToI5hYA=!>*`W#ycIoJyNjwZY5SeO~@Z4tA%8M9vFl1$2tC6-?X|IJ)8 zCmS{!o-U^{<26dv)#GYZ#KmGx0la~I+4|u={)cYxO3N{SvBjgRxtFIj1|C`B*=Z)& zaES!O)u6d=;eZ1!^B=y=7Oyu#=d~L8mAq#+{wTfPl$)JD@|53nxGe$hNc^Me@#a2N z6ruS7em%{xhG0)WvAI}GUpD!WJNZ*FU)Y<#c{t#~tcK8N@*Qu97ogW1w#A>u4TqBM)fATVjL!Lt!BQU)^mKe9DyzHwGqf_o!@!Ds(w6`dS#-|cXqY^S;1yJn^6Fa>|u%tfG!HRrAU!AfIk ziVC9vl{El2nOyJWPI--pL;FRzSCT44V>oGMsJxW$*2!~NfvYBRt8GWvIWN_)J2b}6mR&%@UPE%*=1WJt1$HgwU48t_3_ z*B~)7V&S9w{5%+?P+s@vRxrqsAYq-l{=!>v1->oTv%;HNUHtwH zU{@_feGvUb<+w&aszHCXu<{xqFq@=2ogvpMGx<)Nch1&pis{$zC!6;gKM{`sjd_ta zl^RXjK0YhMJZ)~_5tX%-{^Dzi24I7dS3N!fk%TmCgqo9Wr5)|${jVdH?xUla2toZ& z_oc9V6zL!ZMIaR}2Zy}_u6sUVqBoLWMWHg9@^4SQ_2D9fxJ-nflv0WUU(lByTO#V5 z#qKs9r(FKy?F_kXAKqeJSLu7>Xz~Rb=YNn@4uMkm<*5TylFumMmL7E@XtYogd9=Ibj_2vj~>(5 z7|qIL;nSzB>9ZZ6W|-?7q!{{Ov=1M%z#~@6f@^ZG*Pa_zLpTpdcu36# zyeQOx;M|DV=mNyh$|r|R{qR@b0ERT8Cas3Sy6{6pFh!}{27=RaE3KK;(snD{kRV=i zD4|)C9lGRkI8|OSQg?RKErwe7Kw}=J&sRNj!|PneOX4oZBekJ_JwBZ+vgD03)_ihP zW}xH9oMKSL^O*Ls4nl4;dBv^>B)*V{Rz?0&?6=*vxnxVU-$c&D*k}>wc^aBWcn-b+ zCny1CG^`4L|7r@?|vF$(-f(%3^8lj^f#gjZMY6iN zq~SYtupcT3^u(-?IN{ZySD-~jj(nLqBYbh$!aA!K;MOk8u9~x=5L>vRk?-D0h7rCU zvT((W41%Y?5!#hW#z9xkLZ2`+PaF!?3@JCTF}oFU3A6pP;&h;M5Gy%pB95omAU3x1 ze$b82&UJpeX6gZ_$?E+|ayiMAV?$kAZbSbkdHhYrkK4D(A>g(_M{Bp2*CX=3-rAE) zs6(6i8v=LjekPuu#Y{-OZFM_fKGYx`5HvJdCjfDuJW?wR&h-87LLs6d(;3sRRzaAs zvw19HWAWJ8QX;Zko!SLTq&n#gwG{IX(2n#8=ME{>)ivYoVCg%-LT~Z4WNNMNBrk?Y zm=F;pfh)9*vz6tXJa}GGdb(a2bzMIw#{*WP@{yS4WxEUQ zp*f%2cgC>&cf|%XzLV7O*BFI~!P?d~UAgJK$@na|F(_jU)$&>5Sa|Nix|xr4JR&@` zd(yxT4vbwpCC{ea8ywH_T)c1Pg%P@!V#?{@OSOex*h=0-HPUnI14i1JQ-VDO`v(^M z<=X$1tayIRJFb1ZMSbsZw7Eio?Sv{_#c=OF??_n zaV6H?mrZc6_vtMx`G|hfG#TX z>W(4x5JoJ+3Vttl^j*xpoW-oaf12*xXuX{j`+dII{dD-(LOI?#wO z&LMD&2d!eVVK|a?%Cg6_WXr5Gy?J-ef?8Oozx8(v#$Ho8`xNQkk z+Kgw;NSuuBz-0cM4#lk3wyZP0=y0?>ixXHW5{?4e$*U3Do%>@lVsk3F5dgO!Bcsa4 z5LF+3REX0{|5JMcGx<0X3hiK=kS3EJKoNbv1J{ZjPP?s&!%YXk^q`QVbrDoF^*k+_ zJhvaaW$|+`Vw&hqJmJ{G?QFje8X{mc<|E&WojPZw-k6n$PO}VAL|z7M2&g908^4KJ zWv~n(LZ5p>_utKQ7x1}V>^jG%D^GAJi$(+A6wFU>eA?pLq-80rus^B*3oG-U3~di_ z%<(EQ^zbW-uZHkQAYFmdWW$Chy#ws~l`1thoqCoFLS>UeoC6;H;J8sdfvw^}uo4fe zRu~sw58WtNlhg_F_Uv#U3xBM-RiIL<$~KVJ4)sF1`!hWkQ4m-tKM*j%oVFdNEsf~Q zu)AN4c@VL}Z`22qWl$^q;0T4m$Fq4~vIyTSJWJayGX8eW@V)8d57^mdXX4*mwtNWZ zOt3Ndu;3Iswp^P5!ewKG`N-ub37veJu)kWo>c59c8GI`D*fPQ^r+!M8&zG?`i&6xg`ZFE4*N zweaT2_5p=uI+sHcv%ZOKYq`GH15?a$ft#R2IHxM9l>3RgpP2OMQq#S@hUUzJ>8T}f zs#!ogB(-VO{Gb}qpb~PdzhEq(dTSFTeqJHFTYXWgQt($V_{5=SVbru5Xtjcr30T(1 z>_g9uHYQU@s5iv%uESIH9o6;3SG|s)we^~nMu#54pz-U(Z13YSOof%)MUZfJ&(PUU zheuWcXDP3V zbgYO&-P6}`?Y=bguP|GPv(#2U^KxdhKzfhf1*knLdc<4vH24~mRHalc2u^djS&s7E zIprnL2cAW){}uxbxObHmW~(<+8w)}UF2k0) zx-j$-E0uX44NmZPX}&@fE7grsqI-f!X>MIL=%G=UcjUC5#x! z_1+;mD3i%$z!abT+e0<{d3-FO@WrOSo%qF`E#7Pp+ga`4RQ;Cl=79)pmab%ov)*y2 z5R3H-yZCH!Fc%i72wMg{){$?I4$AI!yv4AqfgA=l`PIhh|Cm|-(s4zm7V+!TR|@YX zlE)oa;1TYBSlbp}Hgxw|&2VcRm+~N@tHx##^$#&8$t4x# zeictex+}V9ORSs!wc&nzFb>JB8?gLL{Gnze`BmN->!++e3akC+IVNr#s*KdnH3#bP z)AZz^OrN|hSsu-c-6Mx|`VI*ZCu;><7wK2S-N}}faGdyO6Xr}(SF4`Of> zxqWZ!hsyYm##JO}69{mZ0-9`9IQGP$3MCU&cJXr==4$pyQy6{7#4IgKkh3txLvz|? z59TFBU$QTX+bqA1LpN0U%?2tm^a49_MKiib@1})b6->00cfdkLN(|@v?et_0+&E2$ z5g^lJb*ZrsrFlt^BbUrMdiS$Kk76TCW3OVj6`)x6VN zZ)19_M&LYFudimk*QgAfhZzNHtT)3qcjjkpT_6iS5yO}-q18#`XYvY@2@RkAeu@Fx zW>`<%d%dX|9VjOWKF_SPJYY%p{R&lpgVrb`ia#5-m^j#@rm@8Ho=-%i%zZr z962f}H2u!|@f6{oL4mb}eZZ5>UvirD>JznG^~@FMK%LFG zaC(8}8kh}qxz1F0rgUmDOa5_V16!oZag%vlUgz+Dx!_Wh?62TVgl`op*NG|6vfTL~ z=>4AlGI4=;BPns0_T4Il68r6L@Ic2*X6=7iLMakWMWX%{y48R7a1P{*G5}!pSw2#o z=PjaIg@SBpjBaQO|R_R!yH1<(Ne|LuHUVi7ohNz)eZ&vn^WDLHmMBT+Rm08*RO@l4!rHPdygWD znSTAaSwJmIaceB7EAz9T${GF{_lXuXpBxQRa?wx)`u$ZGysJF*$reE) z3QBHN159s<&&2CS#gC-ka?0w6f+y_ayL)bjegKt{C@%!QILU;m2wI$FyJ~Y38%LbP zb$<@zxw^EcLQl>jK}v~Tl>%8LbfGT6y1;E# zoMmzVmhj*GC*97&Lm_tMN<6CZ1t(!n6F5|+49vc?_KB5)Zn_|%(IT(ue8$6)koxwP z-BGg(ZRxxf$Fyv^`_fNJMI{Oy%6IfA=KlNOf_&>&0ui+KZ$jQI=OiHi#1CcX2cVQ6 zJOAriv_ytA0(~?-)$}N%KM4cFFpBqoIUeQ6ML6|;d-Owyyw!IKiWXinywA`)b!G1l zi}pWfuK&(2qc+vt-6;MZ9-ZcqQ9`xn8@v5G@>qU?CBhRzlw2OtrF%-}2Cet$OKAGV zRwNlx1NXLl7ieQd;CxX|AIoFi*lb~h3+}>*UJFKNjqcg|EaOSKl>a=YQ{1%>QebCDvWs+2ApKRqVO4qzY zi!gfYDX!sqaGi0n*IIl_l8{((P$1Ya;S%e0lYX*y8RgR%774ROro`nNpv-H0{M7M z1DkB(_SoUdZ%F2G0MG9xC0k0p_l+VJ1p5_6${f?2#iHnmby*n+W$PN}Rhhx*t0pm? zCDjtWQ|;w-=GE`Pq&3fNG}mhHQZY+EF<*kR;TG_uSKHc2;?7Uqu5M&Y=~mvH0P;Lu zcmdKV0&8}y6>Jd&^(Hn#cZ)pdila!|6q_N&Cr7k}W z9Dz5BD#^;1uXqip9A!FRx(TI-gJLTAOQU`gUN2Hk( zH-&f8&#S15Rjl^(r@PnB&--*`Pu=Lfdvtqcb1D&{mMEdM#T0*+5yD!9nabwpL& zXa`$-Nzkw#k46>E*k}V`RaC3h6*80W8G~agu#T@m2;#QUC7C zLdM8a?g~m5TyOzKYY%u4h?T6oOx!AE5x2dXFLZW}*aS%@{*Tb+7GT~f{iBjC;dJ5d z#cp&{%M>4FVnMt|d=_aV3sm)7walD%5Har)4?e@x-b%1I*Z-`f{Mvm%uQ|^!AQZf< zi>P?k$4gB=W6clVArhb017U)@FH|%cJ-t|FHgpR3ZVlT4Mhyet;urR-+JfQm=sTm1s_@IAq~i#)kmb` z>5RcPO_=A5*r-itT5Dtv@4HN306J*$s=aA z2A8oMrxK;i8zo3^pKH}}nN&_=-`&58M=17ja7yElkcaDUxi3miU)Ktn-yii@rLd*2 zRvr5itx|lIhzTSJc_o6qttUqE%8>&QOnuUY=CI0;YE+b2NkEl|Ggh)6M@5SWK5iRE z^wiKa_xq`Ai&l}&MqeIARPX@)YF1%a$4ykVDP-LeWV=f8S4t;C{ii%wTnc-Cmw&z9 zkY}r3@@&uYnM4o=8@)blq3h+Ds&Jjv9m>sl@dcxA?|`-jcy>ANKf8DslQE0{Ht`4* zs-K{hee9A+(v-$iB!@Jq8wIKj`IYIBZzA1Kf^gN<$F zujPo`Em2P<6v1-Ddc};`ZZUx25lMW;pnwZEb(+(*BRSM&n zJqp^as5LU)ukG$!z3|mAPqg(v0pqXctci>Bi7K0h?zg&!hM|6hjXpS zzO-_?GE6wzL6ZdD^ekZiU}ci{LqGb5g$Qm8DI3D+;Li}R zc{dT=4Ou3z;OK?~=GP}?jIOcE?`inbk2p87=A=OCtzJ18c!ZcOSvg%p2BI-~DuQ8l z5QC$st(=RQ?d`>1-`n|bs0jzU0W?zg{np(u1`Qa!zen^PQ3lt*^dYG4qKyicxV#Mh z>*&!iJ~RLt<0P-CloLCUgjwB&WLfv&>e+lar;Us-eucCI>}v0fF_)-L(%4#j_}TWd zU`2Z;;G~g6jgO{JAj)P?SrG~aT}(R21wo*7E}VKvEz@=68H*sC(fd}KfZxGvDW`;~ z>75t1+t!5r%UzIUJ37}M+RatVd72Hy4j}lqs?AB*6MQgPk53)Hk@B4pCr{hM9j=VHg@{Lp7^eqwPd~qF)cQ1Xv z|Mm!zw?KBzj54w%DWJ96zutMd79AHgCV0*$cFi-SDL7HA%rOxzr7u93dc!-D;s*8U zbqN^$-rNh3Um9yW(c8AQ6?n?<&r_j4q62}A(Si=cOoO;YP~ zx;3#<~OJpsk!V458-{_qtr<+IS3ajFn z!5K34@K<=J>``JUuLStb9M0T3^*)~^sFySQ5pciqayl@8NV1j1T}|zALvT^glCetK zi|D88>H;Wp`KeI(OIsJ=d-f_CMQ-6oh<2e*yfBu_>VkTq{|a@Dp+Zxhwq5s2Kcy|N zCeP(oGqY};=&hk`x>k{ixx|}8RC(S?(_UQ&MPM1{BesCM8j0PCQ-NxdirLYvAo)C%K>E3lf?G$Iz@+q1( z%))*lcU_ff0W!imM~4ry3tXbcX)o|_iys_a;b}%mLGwTG1#wBRYm40+RyB+nTC_>g z43fqtS+*7v66^5!IqA?wp^g{xk_ppEb-=UxJwxhtBL-P5iV4m?>x2XCx_1>5$J!JF z54yr={Q*~tt&&*l%psAbw4TP4T2sHjbAY~Bm-j0wNQ)(s3q!*=|I!8I_TFg==l^hp zFY<<3ecdErjSZsGkx|eAg+7PML`H{i-5QG@?5z@scbK$@Fn)UHE9cnrY+V)%=i$`m zR8T%_?5AZfE#%9}_-4p3tvs4xIeb`eBv}Yl+E0C?#yU}R=fmLi8YWEi3c)LUYO956 zxq{wSVZ$^%ym!t612L~ikzLBzSsIxElfbhpjUUqnGLc}>-hvHOWOoTSIZSj?&qX|& z6z(FQqtO)q&!zjWcL*oQ#@O~BJ+Td)j~$9EjKlZOdirWooMYob>dxa{ z=X>1ZIXe~m;&n>%L6ZSQqcwLb%;B7C5>QlhsemViustNGvB9oBCJt-)6nnAHEgjge zmjdODhriRkOoSIflExyz zfs+Vd5v@YV=$PLF_QyXr~3b46&pX4ps54_yz9JSj{6hJoe?g{?`8X>Y0BA3hgxknPPYOCrVZu)7iZ`B&({C=eOjuhy=qgVwxVLIQ7d*3E3{V4 zgxI^St!i&-3xe3huG)L=*t2%+*?xYm$Mqk4Z=W0I&Uu{kKCksuGvwU-==pTlHdpuN zpnE;oMCh|hQ~{4i-Ih_=E9q_I1rC+Bb@W$y;%KHHeTVw*CS=T~p|n561gQhpq2Vq` zLtcgXZ}av@8M!R2tP9y--mSlvjsY_AC`|Ucc&`UcPt0=@7$_pZ^Caoqe3unl>kTSa ztj*8Heg4)N^jgpTgm^j=w7F^$3D5vAnpFb>g4c%R^r};yG&Ljs8Mx2iKR*F~Es>N8 zgXeth`)OzP6$8`$wn)?`o_5YLqmfF*<7)Mlqlhx04n+?`WM}{6i8NYq4j3$OHBEVrz3=(d+@NF0 zT_3i9O)PyEq5d?rv+m^7z}s3{VZNNUt;jP|h;uHc)G*pRCaF#!d$C6$5T9X>%@a~Q-H^C)T~X*Xj&TkCnLWc}a|(~%1y5Uwm##!7LX7Mq zVo<;(RiY3iB=q@f55_w#=-+(DkV7Vc3LmEbb-BG?Lq%+oXvQT5_Ma z`Y~@6iBu1PXDuugUH>+G_)Mrle^mY~o?C==M;(=fM-ql5191-g#$-_9d`Hv0q6Z7a zxc;Q2-t$9gq#ctM^6@4yZpC>5uZ~P!bFHJlYmCltPI(Pb@ytkhAVZbCYyediy0f~B zq-`xeb5H=&( z0Ez8`r;J+vo*Ugx{2MI($1iFXS;oJ(S7=h5G|;LX0TCvrjbp1!2ill($e!D}teUrf z;MYXab-CyJi~AJO+d5EG;)ErE7hIg4^vNyKOl2;t4k3(kNN%1*EFJyvAjGPwh55?K z*(>#`Bj-7$V|1n*_eKG~rKU9}+f0V*!dYOCqLCSuV+NuwJX=L}-UZ(v#^{Z+CJweM9ZcirItGCxXC5nT7(t zI>pp36Y?hH+!s+rFsY(e7yDs)rt}V*Xe)GA;3osGfY}rqRH3h$bSOW!>=h=stioUL zTV4T;beWuMB+ZLb;L@+Hb_{idW}fN`gl*@13Aa64y6WNA#j-2WUD5Sm;biv_MDy_v zjf81OqNY0jSCZiT(WQ>Y1c}+;^SYSKm}E2Y6c6MNzJ1ZbU$7?C22RltQs;ejSYDG( zQenLizK)L8Pz*ThTIZm>xA16}xkuFRWfVM-stM@%s?anjDU?68gV#ohgZ-|;$?xf( zZ)vyB_(KjF=!uU6==G8=I0ow;<3^_BAcD>E!om_tY4VDJcT~nQkDrb|0y07v0`Jf0 zp86fn5~xJ_NIlfzCpoH^XFfZ{{nlY$5(i|&3m8{TAll~U@kf&EJU zgYfmSh^16DLDEBVSd7RQ2nXd>N(VTeM>qw%LFv<^*F5k#z24)}XUf*iAZq;3YJ9 zmm!skPdcdNt8J&8VWA)lJk{_J9?+t1Iw&RO9kE_I$7_T_dr!=K+DT@FX9BYn9ha|e z{g6uULlA7a^Y_wauto8VDSo|_Su!4qipUP(4Kq+eD7O@#qRckcQth6qjSpgJcU3=T ztHp^~b%!)qY_--u8=R^IvZG>j2$KAAB!6e9_nA9-kFYL!N47uWs5e^0c=cm$voAss zJge=3*e5ntWh$dJd}HBdFL)`=-wr+n9HvX9)zbf2+FE9hZhf;LC75)fQ(mScZrIB| zKOK1|<7hdRT09#n6BkY(qmYLVp_ly+E9w4pu@GRk&=(IE_4EV3WqRu;n`Nd3m_Kd^ zC;x(=Nj!P{_YK(Vc3xw4beIL3bMgD?61{}wOCBy|zg*Od08Q%4ZOIn%7W%H6O$W4q zbNX(EYJY!!Rz(C3IEb;rf(mW%tMRaS0~gv2lTXGgi=_U38_HPd4H;#uBN*uF6_Hdz z>k6r4Db2bmbqsxPj;Ou`k&?fA;9ZNklneEs3SIN1>@9YX{;S#C2QwG(h7yD`$}{Z2 zad*8qOml8Vn7sju^~dQhbC1b1%iVIZlJb$R$`MwVSCxr9>JgA|v>{;YB5rPyJ(MJ@ zS|o*EYSR|0ezC5dP^dnCg3(Yq;mb)fdwnI^wa&X-qkl)@A0`ng#Vq&ifMMvRh7Swzk|7c}oZ z2u90Sh+ne*{ZTA{WPi?Rmqy(x6i z2!|-lz~RMDj^BSN3FYVoVlOrNdaUFNcsV`(8tuzg3YWFjsB70py$#`h#RJYZ$|Ywa zz&8Rf<;hN<;kcp#<5@gbtkD(20)K7OhEv`&m?|W&vUXE;V#Tv?v5s2phxjw1k@dYt zn@Yrjk@o+}O~~T*sqV&zXY3o_9yln{*c!76@^KW8c05+K_O;7=$1`y8WM~{m)xtFm z&~#|6KCa5REK%8Z^BdBN5r(Lc)*&&z zjA|fq;37_(ayKAv%97W}oa@GHSlqv#Epzedc#R5`YkkBB6-Sz0rvC2|lT5uYt|}(v zz$V{V)@|@ep-GeVbeUE57k{xMdor7wGOVUolHFe46oRx3$+fxy!<$YFBlAr5VjM2> zCLDEP2I)^IPZ&EWw@S*$K7bTD@pn9Uz+C~M?<)l*mu6V}uKrhZgLkB1$-^F6$lpgT z2P92El;k|pJ;}AZas82M3c!@d`2?7LK6#u>Vb&LBMt_u@0&uo451$^Xt^)+gAl`q$ z>(n$dBxKB?y{2eTLq}7ZO5AGGdA5geIC)kJ{zxiETa7gI<%#;55H9plsJu00samx)w%RY)10IU)QScwCeydAO(oaHdoiyrw z(2yjNt^Yt5)kC<@>jP`VY>v5+Iw8gA-JDe6=mgLga-fcX=R>S{*RVlK*=g!y#av** zv%aUPCFrfbKtqw$kOvZ&ft0RDfMvo#7_||m z-@hU3gAH22kfD8i2R>`?)9)fnjDD^F36}cj7_=m!LDhHoQc6`j_LH826CbJ`iGx@r zvP$N1@#_E>`d-%iF$~m`XjWtd7mdFAZa+kn3#{U=uVc`Q7H=3(|5Yrb4_Lcho$_(k zA&ljJ)_=Jfz2F^?D*>axcqMFeZ1n*T&i7vj| z62pm=?eX?LPKODxVsVV=5ZjvCOSwy3RwN&MBMc{>_{sbCZIoN@$ym1a5<@h1U5KOt z)=of?4uB;mVE4~7|8$ISJ?J)T$ONo$)f#Pj9HHUfZU`w0v^f^rC6J-?n{Jy7c-wR1*3i8{9|>671ssm#ouq_)nsI3Eiq~ z-IJ@$_Rkrsq6tpXEEBdImQzCYDc@%^-6m$UZabiJPo3figJcYt{vI&HReFyTmk!DD z{`6T%2EY?0EdM;nUig{{NEXV5;sW{?`b&>v?k{f2I3{JD4(CJ6{lGBh1fvn&?jX!V z_@nki`6x+$&~KIq!+2oU%exeAEXQ(}R})d!@~Mh8f&BGN*flTV&w&{rAMpCJU<@2% zb>)CM1Vh3>!t>Q}OGq@~M0*>MhJI1 zx-0P)QEuoUSo0bcuvtp7JFv8CiZv&9?Ye}nS3sM+;r1bD+V90Wq1;6vRcKuTWxgL) zoZ8v53PSCcH|GH&V3Ef;p3cGTN=6u`HYZ-O>HDBgQ$oILOI>$yi4N|qo!?$h@&wd- zl7o3~9fRK2Xlt}&tkl)BO>9$lg+lU1E&H{(w^v;&%vyX5JZ&CaHfgKc_6iw1cOvJK z`>ShtCqAVFBWzel-5A6MeZ+Qlw{JX=RF%G8OCV<1ye_a^Q4! z`=lJo5+_f9;e#gMao4W2!ZRA6<#(S zlN+-(lk%sm69a}oW7z6nDESdo@jtdYUn5J-w(t~kn6s8a)^TLC-Nz}~U3hb|JO|Gz zN&{v_1bh9#Cvz;-bbAc^vnE`uwZ{=mq{~``(X(Qh1)TzOkC~z5y=A(t2hTIdX2^w9 z_xr~47b#bE#|Iy)WNZ_B8>N`T3IPr9sjjDu(p$ej5zI1YGdat(_M8Nl-!C?KE{FB0 zNErNS?btIJd$N8Gj*n9T=UMZ%TNOB~2JJcJjTt$&kMc!NG#;4*Ycv|qQQD9hl=|qU zGjT}-2rOQQ_U~3?L$5pnCWdA9PW1Z{teap6YZ7y3=i&C1?KnmsBJul!9b-24Tz=c` ze6O>m>AiZB@_AG}DfFn;8U08PbTFZZO0g4%LJF#=tQ$-@--RxkqD>Z~9qRjV8YX`z zYQ9cpaR&|Roq)v3OxFLyk{5!}!=FpbIZ7MR5{r1mc<04HFoDyl^KO_C8o1<#3dXF5~fDdWl}Kd5qa1v@yLEr^G85)gDK1zU3?^~ z8PlI%vGa(Lh9GfbjkG681V7ENz8%s)HF`AjHR;lWRU57vA$Woj_nFXPRCqun;|LJS zG=b52Y99)4;!I8DM^&T>p(9yGntWm~w-gsE9yGi#@I}Q$gk`Em=e=W%sg{ip99gJUUuzzk+um1 z=DQ9n!iA>qRWNW}DmPP_XOsJu$jM|@vgu?zES9WTlS==T<=2tr#}mGN#-2}K+D!8p z#w<=Ukv)x1G8G7Mdji3sJbupXP+oJdKlR=u+rfOqHPJvI{tqavg zROROX*5Ts88-nZ0#4ph|Z_%`5Dt@XEa}1g7t0qTCo~g_eQdwLz4u(U&lG%FngwsjJ z)@#&+jiE zgcr*GYFfv#xwLYp0m2=>9hryE2?Ob^GJVn-mM-49rTyx@8Tzd?;KbyH8DM{4bflhU zxSIK5mC6w0_q~}#Y~$tE>6>GB-321{RLTJJ0X0!43FxZU&Fd-qMKgFXF|3+9Ny=$( ztrb%mqchp`MIUxr<+J4U@W%xS1Yrc--38Bi3nvT`t2V^$KR z^sk)NT~ewGA~^>;s(E2{Qm?z%Sqj|tk51GWTOameE@P!j#-|)5x-Ku=^p1s!`;UPT z-QQAkQq|{ulR58V9}eUl8kh?tjiaR!%!Ifq`W(BL+43&QMz(^qKieqtJ9RV$PB_Uw zG@J)3`0~oKZ~^6H^Ce<>_$mIwYQF^BlyB~GO+kf2bDy^jJL{eL5OZGo%X?+0!_p2z zvSMHVFlNT>`ty%xJVT}{tC)L;-9Z6$C`jG{NPM#5<7s0@Tps@uH7z;k9_#nB=j8=O z2!`7BhuTcNDO(v{yKt~meFqz(!hE47!+o)K)}Z zVEZ(;GxH|>how!sDA|YJi}p2EA@SWPTbi*<*K5F#xt)Ip3|a7@@5x$_Ql6OHdsD;e zbeHOG*E9N{s`8lwWq{c`-dwSD*Ee$?^kHX#Bs|$md=9U1Ta|}@s&=~<;n2SPVSDW8 zf(Ur=@?ZL}#Aa~`K9-aJ^=}7BhbxhaJM)pIvK1>%MZ;9n2yz17NMdS$bVWQ`ulW1Inljhb>3wkkvsM3DGpwNf-p7N|t zJhR9c`Da7ncR%Jf%j_QpmmvRc$v@w}i!I|^kBuN^IL{)OmafF=d|7Kr?M))%*MMGe z9kdbAnVfvA5S~iP|4wYc=o`;Oi%mcH=~QRRs(g0!ICc-4c%sYAW7GM|NQOFZf)E_L>-SHV#2mBsQ2cHKrr@v6wW-~-%EDQ;><*m8+R-jEsNVVwt>!^KzW(Z7v(1-n1$O*`rxa=KwePoj?aen{*KCd#)F6E&vetMoYhy_ z|6$plUT$Se-M@<~L`h2Ea1#)yJyBj)lM240nO45M@%G-(lq=jNf3Cw4y}18;J~qdX z_Y}edcPT{jO#5)~;IjM0>L_M%|! zuEs#c@-JfQIPRC1SJST3(Tp=@L>}eU2ltUVUU6ZNxK$RD*-fN16{Wbg*Snvn_al&T z8OQau9k=kaa5C5SLHlr8 z(ooiqGBrPd&ADZ zPq6n$f=SkGqI&8~8G-IfhNz^ZG z(rZ7rrf0fo72CJWHipmCvx$#hc9c`zKh{(RsKV}bL?ldbcBta)^oBZ(M=XA|jFF?^$58gc2ph$=&Sdz5}e1f7W-*!F*VEa0=A% zHM|t0I3l^2iS!@#<-MeNdRy{fW*q(k;$l{BrREHg*qP%tEXPQwhC&8WA`W6SksqOrrncH%^3e^HfCzwl4pR&ru@i+O)^Xy~5Zaeb8$vbf%b^Wv!v zfAhe11+b6snM5GL&O-^R~@3Ucv-im{ySs*HwakSuk%Ve&w&wYj|~uTKqyy zz>C4`5k1!JHK(`~r|L{ze14R=F1}-WfGHQ#c@?J8q@o{jgEyI~`3To`JdvS1CQF5`lO*6+A^J)Rq> z8;ES+U}_pC96IWxbD}eikxj|Api;-)dP1%u;=uZAb%bqfm@ezivE(XRwKvxqa4E*1 zC8raHmWzv?Om->)D!NWE?u3@9>deurPL;bp;IM_Ej# z!txw6t%e2j)qfM~K5*kZke!+I?$vIL+0HAOB0_#t_j~B&Gg2|f%;ZPEE>Z`JxVYYM z*tFNGv)$rVpbd}NE=Btoj6Y_nON*b>B%!WX`5#{7U{Yg+rGC;E$*ew;@-*6tia(gd zEoy0#bJxD*D2d;$9DpvA2;Zadma=#aWB`VBP$CtJFejjP&2YjQDb0p;uVCt}aI1!U zC+=>3rziRf`q`ywcy85Lm&g`g+Novx>O}$cffH|w>#KT>v;#PrhJievw9t1@O>HSI zm&B|eIn|X)mDZcLSm{keO2jb6J(%e^6$RNvel+E1-=OSjDdERrEfmk1u8a8zli3f8 zHLFgK6P6J(sy5-<*TA%@b}*~k)NzRiVFS^i>ELmSsd}X>n{o9WX4!=ld%9jR%PJJb z$@nV{_K}!LN=9evB(7wBHN(IWpc*)}eT3tI zy7#WdR9@{Kc`1|eyeK?t9c!Fv5ftA(>TWxAT`o_Io=kBxk{_Xp zEghW-uEZ1ed@lR>W8cH3{Hd-*+72@;()uUku;Uj!JASTE#_JESR8ZCXd5ihS1%Jju zUwm5I!L<*K+3M!jInL7r7`Y%+GSdU;ymsakswGuCS}ksJa-6>e?^VOS?W0L8crWD%wchx4-nmNh zGrhF);rBv)Y#YJA$AJP(r& zF)8H6_lNCyA5VoyO$(-$im^k@Ie*xhwU~LhI~vS1R$7T^lcbDrL-TkZgGKQTrf6WewXJBmm&4ke0c zF~J+4v(Hi7-F^JcKgr~TMt>R$Jg%rQK0eZhl*OVVp-_z`6BG^i>@NS*Lagjsm%#H{ zHrx=|5ZPE?xPZuLkWRh3VQLyuVGyqQvFFurCbe|ZK+I-*f%t|$n2~!23WST3IK%^sq}f*Dj5fT5+9tQ;O+0SVkw z{)p#)Zgt6`f9T2Kc}a!D0kr-$>*GGcFE;>Vaigt9Zied{DSPIbbo2902vV!VjRn9$h@X8tBc3xB3yLA9N-V6gl4r(=VF&&p^d^5$e{$@_ zXwqynfiCBH^fxtMy=HJcR$V1$(f(#Gk(n-CnQb52g*Y8uw}5fe5vSk=It#~>3}BWu zf8iHQr0TNbT8@p0a+ITHFu0s(33~8JasgVDGF8bTz~5aQZ_R4MBU|&h9QHTUV9e#Z z8z596-eu@$`u#n`)5yLzJAlih4-~gJsIvzgJ&>2dOIyrCr)D|R#b5j{9*;S+VRJ}b zlT}N+9w+a)8fdm)E2$8sCY$?gr(M4=kHhiN$H50B$*DeVV!wF0S(%I?La4*l6i5k@ z>f@3MiMN65Sa@|A)iI=vN{tSLpn4J3xJ;#%7skyJR;|<@TYNqVClCr13=s_^uAD!? z37D3XE{xuPb6{TA+nX}V4YeCd3{;JX{1co&hT8|yma3@HpMtcuskdNh^_7%3cfTX8 zI>oIHFe-Yp_*2Opx|n>b&}cM~ z1HXNv$G+~LTH6TIPIH}d^FO~NcG+0qGu8#U(BfaZ0#20k!Hu6l10%4eM-^t`3z9P_G)4oYiJ#SX8 ze%r67JyQs^EaLT4#!jK@0_&@OuSgMU_PT?IGCezw$J)cvHw7QinpR&$SEWfBMC^~> zx4lupSDaR#&>wan4CD*GstVIX&iu}ZpDrNJZS_dyLHfh^pE_O#{`gx>vo<}-6zQL8 zVRK+QxW&QW0Ynjfz*gEfnXS%6wt}?RncdZmYO-=i`*1>szH$@!&(%go;Y2+JOGnk< zgu7c~A1OqPjkqT}ac~pU;+1zv&Bpvni1p2m2ptX{`0KifMPnkX|94f@t?1F>ZBvna8;$|wdAhIrrJs$p);ok;uROfffdEcKd`)(YtT44{m9b7;Hbrjkr+E5Xm2%1QFrc9xf zO?OqC!`*nCL*mP~wV3QIyZY+kn6YR~PLr^{4D}PUzz-!7;f-%Lvi5yR3^8b{w$Od( z=3|bmwk2gAE~S3$y{&d3_0d=34?RQId7Ulzx?PII5V^PNkae{}^#?ZABUO4A>)l(g zFnyqIUnXKGnV%tLr2djWCJx*41{0t7}5M)I(L zAZ#-NTJp{{&u_$TN_n*Az86O&p}(xBU}gzr2!TsYP0yStk!aOrj-e=}geN+ARR;$K zS_jANA1)nVxA1WETKjX)4IYV~eM=-Jv{8bf0BZuTE4fd(vLEfzx0_nfd7Gk_zP!Y% zajSfowD~qxQ|#e-mNVyue;33%#m9;EqQ%pJ_0mfYYKtp9d7(~tawHL zF0f~-M!CP43m0E z32a0eot#&GR~{4mZZ$^>dhe&>@kHtkgG)*Q4zeBgo-LV$TwAMfy1ZtOi3Dc3a?p6x zHuM9War()%tQ!(ih}*1czBCBlijz##b7UGH5wGj1Z~n*a=z2;NCT;)lO`wVYId*DI9vR%mQQNpg`LUaxCVko@J^>>{MUw++JO=!hCbTSL6eZ#9* zZpP5~D^UX9PZq5^wab#+?yf`q3s0(?97dWvqDSYwaphVQ-2012;K)i$&cXHt)u2)+;mOM)EL)`}o$`FP=QDyNj}Q{vVn>3wT!M2 zF9`OMlISm5TZvT%=8XUT!-DmpCU?hD>BM7ccgG`1So2Tq|Non%o-0Nt=~zvQxTwPR zWw-nNmuOkqXP@bYvR6c(^`Z|4$my~KehE8J4iV^q`x3UijNl17lX}fQUkw#_UYJ!H z3zGQ=4lY3#ZnpSfYbm?rlLghT7NK|y*skNCeMVrXm%W)M3sYE`>uucjgOSb7SmZmm z?xXqW5q?Od4cuzEK-maBP9ss1YU)AkdOX;C=($*`XCV54XNK@%Z*FLKD6~=22cx0J zgG*{j#U;d;H<*m`cCm?^Qtj!So)l?U`Au@Ow4XmU8&C-IelCOlv7Jdm)|1uGV-mI% z6EgF4&6_CvSzS+*V?%G`n_hbw{1>q%`d{$A?T>DAXz{s`$6 zLfHNE5u{i*BOk7xi>EM0yJ(iAYo0AU87O)4(gGum$-AH*0)n>58;1u>9SuWRqBC?b zmeO~#{~F9Iz07=j`L1qVZ8u#Z9ux)LO!2rMpUDw7PU^=mE0*@9l_qYmvG!L&G%aR% zA$wlZ%BG|Fo7B&RQ5Ec*;`jlxpKKw;ylY=WU0jt!T|L&g2)Z}iMPnL?i0c3QJVm1%lU3z zs`@Bv8{x^MVfUos%s7fh_N?pIKOOeIz~rv7%9f9eUpusnlGf!rghRwm_LYWO6pY$u3xl>#$21Krr z*9~bMHv0EGweq4%$yuAa7id|!x^=f(x<;sfQD60|z7Bonmd@X}G= zRIw7|uwZJxu^0zr<-l|7SZXX-`0QC<%eE;S$;?zQ+>mYIdZW*}DjiCq1AU4w z6LnTBx+SO5*|!0k4ulGm`T&rAg2LCU;Cf9>aRtn;>Xap5T%Ab)LRE(wq>sHs>d6vo zQARikUUJkDdbX^P38%KpQ#AqVCy;yLzP2Rka3q<9no>7#9xmPC*PqkrE%J%iaj16FQ@;7sT{@?XxwBNfu z&B_CJ!Cx5_uUhJV&#e8at)M>g9dB%XiOEY7JLvNy^V;&jSCM)~Vrb6EwV;Jd)HgJ) z|77OyiB^^aMe_mo>8oWN!3 z4`NUsP3G(C_YYehaYgzMy9a@r|43wWKQMkkN_!n7edtOn9k}KAP~vZRQ1|ZkyDy4~ z=U)WB_T7fxeTswr@9S|4#oc`DX+_a!aKQ6F&gCP2FBiM#MQ>Y8-}DK;j$aY|)bc)i z1E9O3PK--d)%C4YF?(iQ4IfHBzeuQhk}^w6EfcKdZPM|mbM)PO(F9t&X(D;;_fC(b*zT4Rl6 zwlr;uIrvt0d{XdGPTX~WQbYK0sL@Md`~Oj_`p1`n5vA`y&J znU%_$SAx+lF(ihJa4`xgAnzI0mBxBosW zdhu_!XZ9V7@nOnKRIlqS0x)5m@XLki*hpCWH(`^?ZK*txgefwVRA(lTcAw^nW~vgmke+;a9ezZJeh0@9fC z6PK8l?`LRcf@Tngw%z9piO-wSK0U{i<#W`7fK)Phfe-|w0|v?)zdSpy%oFX8nx$0_h2iew@6Z>wKlJslFz#r4D-wMBjBICo}}>zN?Xm_g=(XgJq4EchWKTrv7jo39pg|Ebvyyrtku%<_Fd zlp=5+`Q8yn-&ausr#uKWZJj##xRRUxLH+6=QJ`vjNodFQl6^48tBbugY{QB)^P-52 zLx9xBC@h$RExG&BB<2^RS84GqBU}5PfR#_Ltx7s42kPH;OP>ouK1qx%Jsp~>><$Ag z*Jszisro*-n!doYY}ME$sP5dYFG!02;sVd620^3ad!<7q5d%n zjzW9qs>OKXCHEib`ruXCMBf?G6gB50?;*}O`k2}@>-$7rx^7J?;Y7kgb&)R`CG>%2 z*4#A|)$>`+hmQ5hmSF$Vl?TV9XB;hS;%Q~xFeC568yH&&R-QE%MdOm8pXjf0T2#e* zsh)j(v$L6&pwkn{2W+0k?L)ZKkp%qBNhL@ea%_SHx$6V4piP^xn>qyv;6ma% zBoDBa7Tp@IZtuYj%J9KAHX+dZr3N0vYcp1P!Jus=5hy67Qt!GnYMi)j2PmUf^Ch7& zzIOYhA3oWz2}oJMccPJ}CyMO7POjWp z<5}dXXk)aKvs$k(l>Ywbub8=JyI_Yb`|RAMcCYb%7%hCmRBdbfMiClo^0?S4>U6D< zupFsf-3E)>|4Rnq?8|&fB~t6)f;>xd%9m(rJ2C6iEPLJ=s0s}_Pg$&TOc8rE#l4F| zrT!?sb!$rpf|J%EL19RlepKs)_dYjJ`D~IQEY^7R$L^kX)hzV}Ui6Dpp3yBx z-KRa5sYAJN@{zEa6(OqH6=KbJKJk-KtU?&7;T@x`FEkHOCM!J+*=Ki3xgsD;rL_@| z&Ko*_6#>Z}Sph$R3nnUEW(i&2Y6(FfyN1pfF?y-S4>9d|{XS@Y57ye@eEhwiz!YD# z5+ml98E$xT732IV%UVfzpG#;A->VS{n>@k{1RNv2uDf%OK2Bn=U$BEShEIP)y%&z8 zizR;FWK0Nb!CGl5~w*>g3plCzTlWyAP65=8%F z2>5MnOKk~|FK4v6PLOc3ef@Ig5X4vZT@}V->d0L`psRk!uUUuy#%y#ANX4Hdh;82x zsUNCyba`O#Ljgz6o|gW=AOQA`+=+}1P#kXf52FCQwCJX(>4L@Q)Y@UdsS4RDQ-MUx ztA(nitI>Y3@11LR{3lChdu-{huvkvINOcF5wnBgYHVa;)U$&lLHv85Uft*ztPLsxqBv}tw^$K)3s z@^SIalL0nNkEy|%!o&Zt`fqIO7S9Nk^?N_vmnr^wB!L{XRO^Q~DMl%kwd?t3x*fNF z@sN=OFHXv`Lw!-gW)0A+AwoM~CQp+P4*?T_@&i)8GpVF3pg}uFz6rIR2Qgi!ukk-n z>g+rgo+K~2t(Yh>jc4I*8zi?)!aa8$sPRplYzm|jN7XGXHrcN zH8@+UHT0*2d0?9zW9LSraZI|V79dkEqQxwvp#%?R|^;u2@n%jUt-8u0)xOU|ip z#D+2?Dhqmk-y~|3h!;ziUcOqH6*p=w9gjrn9lfo?B-}0VMaK{R8`Rp&L{@sKUVfX) zlFN);wc?yFlQe)OwB)+J%&>}EOnH$%Z{%{h+#bFcP@iCD-fmpav*6NGqZ(0L(K&gf z$IyDyaIr*1i9nRoezO`3>*Bs>>EE7$5omJ?NR5NIQpBLtK@j*;kD!94r>=p$0rfj3 zB8x%@pKrRK`cSmqKKC~5n!g@bWE=B(Kg7R4rWDIk=Ic-DdqNd+S)3dp8r8dyH#^h{ z$T{u?g|O`b2N9e3LG@_B)eyvUP(_Kc?PEZj7fM%(VLF@Wd>9UFG8YivgCc9v!scR$ zRN+O1AwnPLB8M`28c^|+Mlj$)gIewmzG{&VI#~*S+Gk%r!Fa&lh@`8 zfnnpancOM{{4@V<#%kt=M_ISmt4^=v!aKH@8)qa?ZFMQzFhZ64pz2t~x22s~JgiT& zkMh$!*}Ug&zfCpn+ygfOD9&pkb#5aog8r^;v^THqe{)*OWDa-NN>0^NqVkXJ!kMWW zIUP@bn1CWRySYpDF>xS>uenjJC@oN$>+=47h0E)6BwqYWVoG$=uTFq=Tth*i<@ z`D#GS44!xYhSgbQ-(Or(DvpB-C1v}q!sIti*s6p$q~v-HYnSs!la-&pwZDY_5FUxL zZqR662=4yH?~>CzpO&)3zLg!+=Xs!2RhV!ahf0y-er-`zdOC4r+`RgFh2M~nul4I; znCb6G7kIKP)aF_up-%ic(X((lRVin5_N9R&eg#cy!ggQ{ha5FKZn&;jtX&7tEs-xYd$k}@~2a6U=RfZ5lKyPR2 zN;(O^n(JbRhv3q^2Ff@w`zFfq8q%QWi6^CLF z?{DH^IkN07Ht3Jzmt}9i-uXJ~1*y#pPCqB^M4or~Z#QMHh3Z+@8;d6^zs|Ls?HPqp zL~TiK$9GMa6(m9dDWhXnHP`m3(l&?={Z91jWd5Y1k;a{aDTj`WgBOF|aYG;XFZUL% z)Y;6~Efc?5N_8rX@1kh!bq!ycf mpg+wMG2sEvd%e@wycuQ#8kiJiRC$Lgj#%5Gcg)Zz-Q6>QbR(VO z@1E;g_aE>)YdtTX^Lnp!-kiPn`F@gOJ@}uzg=&gj6wGa7Zj;N2w*X5wnFC%-xLqH5 zm2Iq(nVgzD{<*S-#*cjD%Lh{n+qp8M;yNK`<*+Z&AX-%Q4z(f+?^OLo5vuBOd|I+d z@gB$$1OjV!dH3yj*td_z1pbnBZP6bHbmoT%HpGIem+E1t3ImmB`#R$gk)C6@h*xaZ zTR~nL+jNnS$twK8Te|K^4!H$3&$S_gg6PqG76ALZMUZcue$8$&f%X)lx1PL@H-T2K z_1()ik7nmyJ=MUi4*c6PvseE+-3!P(Qg3;Mwh9*Sq)lA4$6lf_Cjm)|u+CF)4DQ$Z&zl4v3s zUYORg&x|86xYRPQD%TCROOHGQD!Z-M?T=Uwee|e&s+AlsJMT%3!{|TTyzVpi!-cAN z(j9%>l<}}5Q8%Di-v(F7Fi#Uhfh$X$I9jzy6>Wc5ON@yY5`6`^R&3eG1fUUiZRiZBL3&PnDcj zO%_zX(8G-(R*q{)F4C=eWR$e>KiH%`>&!^}o@=uSd#7WNdo>zd-K5j=gwh)vq2axW zb?}gks*HRfYh3plNV1SKZwB|vx*>#fvJ-zZ*2ijG59wztRgmHSJY4vc`HWM;qEWJ> z#KCDU5w|V}*ScxFzeB49;#F;$y5}Wb>dRL&>5Lk6TNZR+Rc)@D)bZgfNh~(?cuWUU z`&kn5=yk{0UnDwasYo@2D{RO-9LUHM`)H9E*CYSwaY0~6$9dcKbhrUO4WT>hYb(2Q zKVA-M+^N(jdtf*P(M*0b4rA-9=GYImQrWwsEvx0%BwBrc`srdBPg&{Q_PfWF46{IK zRIXD?AZR!z9L`DpNje&%Zgo48A*$jea{#%uJFhWpDSAB-kxVILs*JG0OB5kOH|j0- zfB!zLWivcolFqk3^+u2ZCFH9@V5H0int=`|w{7B;kecO@{=WFr{;3kQQHMYO+4XM% zZB0>c#K!R3BLX!`+WkZXBa&6Ucom&!?ab#bzBr%PF0`1{lSySBn*k;ZuQGcq6=` z0Ubdbv=*3qy=}2MY_6SzG!Us3IzPxd+Xx!vYcTWSO9uGf;wNwT+otujG_y4hx?90X zZRL9o^AMz=7OJW_G`04*S(XzP#&y|V2T2NBq$?}w(`HTmI(es!h~gZYp$a?*88lC8CxM9@fafV-q8%E(T4Bj` zfgd4``M&Mw3YXaqa6n?%A52Lw7R6m^8~!Kj(9irqcu)Kt06ARs z&Z;G~yIh76o-eUH1bzJR96%=CtfKyE`WW}(PRcuo+W6``%aBn9Ovd$sZDDJ~-UY2` z&DQvKgOq%io0}DUcl%Vtx5X-3&Z=qy`A5CqY=IqF_`as-Ws_i6_x>XXB2t!~c(t7C zvQ_N)a?E{pU2CM-Y>|qwSzn6lt+DKDQHWo+;$!K>kJQ+gb~>s#K;F+P?^l&=`eh_7 z^Xv1#caGG@+&&?f?hz^<&2rGzspCIEgqpbS$en8Kw&CuRds7a_Xlh=?qY;SsD;Qq` z&Su(^zr%(d>E%T`;|?|SWVm%})OO_gA7f^&3i3r>o=hQPr#v{-sgkLnx$o5dvMW|PGiL(6z>p_cHeFGmP`2zR8fu zi}B|%mkU-|GH%xnN`cf{r8NjHXXeAD#P%s~0t@ju-u3=i8c&;l=|0bF-?l+0Xq?mR z8Ua1w@A^QNk;Zmh0c+NEHQsdR6ygl!Zze}y$#^5N4fe@E4WsxnrE6S3Qv^*cig zcQOKKd0>{|{bRDJ`@lxawl9u_esADzH*eg?iWF=8WK?&cC2um4hyamP+-C;|#*xMc2uXWzE6%M6zk+hSVvWaDI0}E%hF0=6U&D#Op;@ zL$x6lS_dVDt`vqYvISPZb7vlHFaNR$kY23Q7DBd3%c4a~q3h`rB+kYdP~5i5Cf1~e zeKOX#n)+$S-bh1&LXfS$z!m1tjL6P5n1#Ug!~xahtv4G(1#4B5_^m zj~Gr7SDxBxLv41{ZH$ZhrmoL!BbW@N>h<)7`4sG*)rBFa?FnTlgh5(B9LbQ%t*FCZ z>!wq*GH*j-1p4D5r{WC3z~ zWG~0|KFo9ehjZ4?^W|CSL!kdZk>s_Mw^ zy;Tkj8yaKEbzia?h|+Y7{gZdOvF`ncsKlf{VFH^5bt-uSgq>a$JW%<5J5RfOPmm(8uV;bUs7o){SxRyy`GdydtQ1?0}k4ImQ-s;xXE)oa%CJ$ zRfGNAyvPNQ{RVgTYVJg-vYM7#P8Jk=Dag`lf0t{PYfjrUxV2CEcZCsKaL?nap3mdK_^4#A{c zhC}G!;Bbo@5jnnUGUDJ;iB_N9BDr`7c2tDj9d0^^N zSxvG<;~$?8#a8?l(CnnsA%pwxnJZ7$kHCa+VGLR5T3LOmuXbG}ac~v4!PKw`wjn;v z@uK+s$4(2mF_}QhaZghGb9Wpf|4zYV@p}irDHWX5`s`?N$oht>rwtgAzKzqqyUVq; z#RdE`+WD0`R6>7OC2_p#yP|aHT>p7pGaMi+o5TDDM6|9&bvuvNq0% zRPow7PS~7bzk!}30)I9O+PX0$=QR6|w)^|~JMygE>!WAxRf?X!nC#GW#>v$p&W);N z%4%lt^)~bGwl8U^2wX&ejq8`1g=J!TKmnm?tbFt)w7C$1~SpR8& z+ESeflYf{|mWpBKFnv5#qLSpWKsy7O{Aq{oP;LqlYHOdu&x7izXSq_5;knIa0XHVS zT+n0-DRdm`-J=aTq$4B(`p}Vf1bUb4tkcQvEP6igU}51yho<6n4%?aeR7q}d6~~XV zu_#=Ka9&1gpUiNsdq?n8g>Ac6AZ?k31O{@h76H%q^tK`}TuK*kHqKLkJ86zWT7QQ} zmY(@ORgA>#0A$j`zxzL&f=&PL`C~a_N1VyRQq@71>>eMC`R+>NJ~d+!XJgJ;u}ku; z{lk8Z#akW|v6)cS`Cpf07^R{fyqt{q2i_i}h)DPy2+p#RHXzzbrlwU2svR@H&=A!f`A@R z!1w_8PJxk@r!g&~d};AsA@&zwmRQS3vmpT0=OY0fGp$zW|RpC>rhi=9Dc#M+V4yo`Z0y<{OFHPvkS^#`S1tDBY6-*yt4)>)Fw zU&lYk8{y6?TkF%oYp3Ws`4StdCQ-O7{ihRDOrrN|wwU%xA-odX^P?N%ww8t>`Xd&U z<$?qJ8&XWll=!TWzHwD+HH`oH)PFdUNKD&`5#(z}h5zHB7a8kbPB1>7*9TOB5|U9{ z+h+?`)n1tnHVVZw_nt%Jo>`kRPF~yF0};12Q?5@U%w4;m$?}2Fm4B98&vm_K;^&NO zhKfj)h5Uz;?4R&VOq<$&_SeCTQ{B2o7o1mg<_VmtBP>KrhJ49ri|)_FL(39v+;nKb z5??4wOJ`#D=y7jTdGb!oXl+Kgn+Eh~fKFM_F$v4V95t7mcaz!9#3_2+x(wCZTnud` zV77ZhkK20C0Lk4H`tOnHe~dKs#mVWF80}e%JS)@6jj{13uUB79nwjcTY>`C5pQ>;h zfp10-vn*q}IAWk?MJclcYw&~X`d)=VtFbca+lm#cm1nkF!~vda*42!Hj7AVB9}63| zflOrbHzvySl-?fF@q4$6NI+|C#VNm?qVe?)8ZWJgNKvq|Q-d&#ER-zIL~^(DbB7S zmoseMt*8<{-JeeP&orOGs;;((>q?91zi}19tcsdyS%&Z;!6L40%x5mtVPiE!CMN{7 z92BUArhA&&PB(03PRmy5-JhD@p)F9bYsR8P;L8dn`9)B2_ay|`m!szXQK%e}y@j+D z6v1e0k8;$Rs+cV$rnHStK#!?OwhEvyh-)ogksTkLRc zdHepJDmOM)(j(12JKoel1H&gSrb8*LarL}Cz_?QHpvP0LxyQ!J)45f>IETQsb~msP z;kta7T7ID`-6oyj5>+kPVkB3u0G*TN)c`W2vt^1=uIMM>lPl$A z1{AxT5X>EZYt$$8jo304wh?-7I`zd{R203;F@6}}DR9zONIIutfo8uXUF3I)Xj7<|`e}3u_0s2F;P7DR zyMNKC_Q3t|q_Gc>Eq?0SuwSn3fTsfEZj=+5Dvd+yisjP^zGz7#H#EE-gS53m{5&eb zbRoNKD7$z?N|-r?>upNRRhbZ$(B2xaQ09%SMpnbg4f9zby(-BtAm z>GykLbSX9TLqrv;NhezftaeKK@xGxV{ea=v*iHB+ARNc*?8rEQD8TdM*ndj``mJiN4SR ziptw)0@sNj@hyheU2T=>Nkl=90!97Ec5TYf)Y{ZyBvI5Q*5M#Se%|^T=NxOf64=qHKX<24&fC^K;Tp3MD+9V~m&2>s zJt3x3T;@LQP|q_s4uR@J&RuAu(Y{*Ts>cUYj}<9>*g!`l1k5>{0}y`+$`{|>3Mk#e zamwwynL{qwR~Tk=3s)DMi>@AC4T#)lr#U$ju+|Gaajzm-1iFWiK9EfZ*i`axcL}KK zb-|v9B1h9+FH^yd$+gxOHR7Q^JPwAu_pb0ws2z(%<&k?!m9?LW*2Hy-UXU!m;`AIi zc&-ud^cw^CGjZ?hh2?2_+*JWb^b`J3Sv2(h=SJn43*R>p!o9QjeT*_Q!F7$EU%2#T zL~!#lv%01g20w`JB45^APHmY-p^QCw!^N4Qd~!Y;^O$^@t*U0muD;Dr&mjLSygcK8QE!qD9GFpq#6>V;MiY($QEoLy|Q*6ucJrO zJ2w7@!w|F|YtpCuZIWwFTEzujvbDoThj$L{thm(`Ie@@dMPr9wTOx;rj%AS;bW~|l zrUFL@EN7WpiNlPl$!-`=@^TdKek+I(mRf?4f&= zER_#X8&KEm$BSbBB!@CiO?a-U^BXjKAeu6uCBOU>z2q<K4Z4lp?#!wi4?7Zv(CjKi5ortNvBoaMjDCJta6s<*8Ov$HY3;a{Zw>XS8+2r zw4cjJo#2&w$7yb%n^zUGavyU$uedjr@GX)zEQtzS4wY1=dKb&=eqfHP)?_%D8H0bQ{R2X{dqIb>YStb9(#Feq8+W^hraSXw{xG${IcTh8HFT8G5&7VlhbWZ zK`&JEuVctNqRj9RpSU;G10T%wdBUwGlEmV1T>?_3c+a}Tgy>XY2zyDs-|QA*R-~~! zbgU!Js1oV&qtYz}_o(cU*|jmtbngE5&{B@T&71{&A={Fz>yxV^Ly@ZcnP18Q8Khd-Wo0#_IOEmlH{(?%xiDHxL5B>_gr3W zsh_js&}XE8y~Agv=d5*T5+t$W7x|^jC%>ZVmz;Pw*r*6?*jWw*lBV{SKI zTKt+~XjsdF<_CP7klg&QjF+(o)O8%hjg>E_?K)PXjrh@pc&#jX1yFp-H)+lRg{xR{ zH^OndlSAEU*qO|}B8^2WwHujkPBvu68_^Z($b}1@^M80_IaTpwZ%HU@m)@Y|z1_5m zC3{@Fd=Q!jpqjbNHuz}{x@5(!oX=mX$jrr~fyWSaMnzZP(j7EoTeu_SR^C{OF035# znzIGj^PX{XHWr!Qlw&0<57l zG0%$&FLXxt-F`>uYU2lfV+yN#ZLXrHUrR5N(nuLD}lcse>?AbDDpCyRdoXcn){l2KJVOjc7{Jad0648*T zbP3@(;2W;|T#3}lsDEjp_1P%NvL%m(WkOPpg`-(>m*1&hrdYc42feu37yiP8@J&&T4k z0gH#X-qf^yZ_*l!Pk~+>?(UNtuqs~12D@@z;Q}<9qAw~rZ>I`JuRExqjE8x);@>_0 zHmVL2{HqC+cp6d;O<^FBSWiv$uR4LVJTlH_b-}^$cwCf&de+Z3(xdF<=nR*)E_z`8 zq(oZy_dg=pzo!lb<*^48aUI_f(lU6pueHV9~!C94DNc@Xxlk zL2tey9k-cs)EkFlqkg&gncU8}{j|)K^Nc@T{tHAjmJbiI7plJy@pq~eYu}jMs=M5m zQ4fX4pb~d@)Zk}@kX9;y_pt-BYh*yf*i=9)Wwco28Li^qKMpb|=<%pT=s;Y{Ap*|W zK2Yl0_kr4JZ~Rr=xe?!_SQQ%$cG{d+`=i<*+4zf;!>5|O3nhc?ODR)B9~o-PUfBD} zht`|tkS{pD@%iurBtcmDhgM1IaDhKAdNp4%XjUPY-sh{k;97x{kb8)0*8RVP_8!#k z{1a3ZoW~Z8Kk<0%fz0)YPT%&n_H)@^Ca4#;*k6g&B(%C3Bo1Rn4_}K+f*(?PhCem zg-pXEL@kM*s%&eljofp!uOuZKh|jDA0P5N z*!(c0oBJ?GuaIs5d`8?ty93tLI#)_hoeJk@|09#^jn5pPr^mh0(AuNhD_W#1_F1g4 z4IM9zk|b1f$~%>b#s4I2oDFBpVrXwQ=W`{xrkb}h7xVn(=X#bY3u!q|p!3V#GW?=N zU9ttz&`@B(bCO2amf6q#eG?U@FzB4YF#cqOwqYpoun$}<*eQdm6ERR$xeiAr$3>rz z=9XnQ>0YbUI+}FMQ((I?+#FR|!iQ!06WF?^Ju8ZH95jKR`wjyQ=WLUQg2RsW(nA9C zkZ13r##F(-_5?9N09A_y^*U?bk~$4grqv3A{(cWJoiCrL#|pUXs+#NEy9 zm|wx`(1(2dg8MRPS4k~+ZV!{SBIg-xYPXi9Xj;1OBd};_i(csB$9ryRm&p(-1IAKw zZ=X~`eIS0--|qPgdn+L6I~}&3re@#;NjMktF_{*RkwcZNQ{y_#uDY>7v|Ad)wTU-P zjLCDB5{jt4m%~LF&KS+Y8I%&i2h#e#8y}F;47eMV@{|b1`QPV%_B|60 zm5mu*Ff{n_M;y|hk7{s#<069ekAh|~E;;#=7j*I#uKBO*bEGCdE7rp*&HZaiSH(Y* z1hD%C(NvGz&JfH=N_%Sxr8RKPVHJfF>)(%c;Kj>$t_N_~mFYvr&-b5O9?IT0biftY zN_RVADc07x^vR*vC4nz?qS?m%jvrpER$UrMs5pL)XTFsGQ!k;FLP zcu0tr6yq`p=!k3js$u?l`1g1J;lK-+6-i-SMrZk)$9ZI?U#wM^Urb$j#6Cf-RVZB` ze}@HM3&J{i>=it5)Z15UZpx(QhALYP2vlnhv-pO1k2X^6QQj=em34puy1?}e>kOy1 zjaa(4G8RZkKt~xLv-_eLihT=y!NCS7tk$hAT$o|2i zsp{M<&RHcZV9r1%{EUF!4H%bCxa-Yrn4BnCRJGF(`L@sYq`u&$2iPO>XU_kf;x!N( z+s92ekbMV551_m9V7SPC3TO?w@L`4};p!cI@82D>ElxX_SU#o2;)#;IN2=vPdAV8l z(q+BB`{~r)cD`S^@x{&OV+^&~2E4{{I{1AG2gC zQu%tbKYx8dwX@s_I{q+V{jlb_wfaG+YSqX#=-H7I$&&pA?aA$BC+=TAUAB{Vqjti|oD`8P1`dQSrtI&akT zL=_ic2!6qi+4kf0jqG;)XYZgD@a}RoL#pNmIO~5x#ghG;HoThORhagguIHSH_k0Zq z;P;#RK_cnBExpZzujw#RG>4oc(hKJzR@&8qC&L~~=7o@ameHYvmI5eC@B#5X&zo3( zGyl;`g|XR#rldj1l*vHK63Ek=r@;An=VtXrB{{UyAh|W_y!m;K_8Zm`DXP41(mck< zSz|$qzVxYdXtx`!euw=Ge!i)RmNY^B`(OO@N{8I<77mBvJA;OImE+3u8S8YON)j>z zHrDX7xb!v>*SF~ANkRr2L5fzw!=);1(JPKdgZeyUfEI6`JSL{Dg};p`zc}x{rc4J{ zh;BK#UGoSMD-@4IAhlcVSQzLCz|%`*A6`cX^oVv@tgVgInN`sEV!-lPM?`%V*YZ;h zC#MBX#_D!V0M*CqzkZkBF(!>)DyR6;o+$l!J*2=|a{7E-ZD;Q3`8ky(@*tbI!Ab=M zaxk{O@6XT|q?%xiTz~I$Ro+{7`~1a`5N!_0xX(S&HrOcGk0COtin?`nPdE4kzw)-| z`_47(O9+P$cTxaYc#&5&9uwa0V9771T}H~q!t&Ht8N+aecZ4m~J7zs0ruK~MgWURB zjLo*oYpY4^XOi+oQk-cKNn>El9F{c)D+M2@TfThXS7Uo#k-{8jZZlK(KFz8PDO(=D zZ^ee2#J#<(Z4M*{K9uVA{)dBeE5o|c(hm-I%W|2qKPfUj8YQ4Emv!4_iJUq2cN0!E zVC&LQ#Bn~=`!OlS3MO*f2^|XJd0y)?Ui(CAh=RQ|l@i(Qu&<}r5ElFqlY^=x>+Yl$ ze&6mpol%$98y(S;!ni2V6;`y~bUJgO1h(|#ZTh{1^R1b+YBtf8e>mrHtEHYJ?A++k zE30jQ?}H0+pk)NS>!#ZYBxT_xhjExU;oZ)?_gml4Vp+ERg!)ZGo0y(hv;lH>!2`Yu zrV|aPiu-j?D>c+3NGk4Wv+CC@)3_Hnw&oni1(wU4p{B&7%44eXKbb+x-D_`nngq&U zidYYmeSUX$=`hRb>qo@#^nivsQ!ZtXM>)jMXqS3OyM~|?y;1#q1jw?!hK;bVv9S>3 zg>!$1Nuh#pRlDIcl8Kn=CKIv-H71A!Thn{AEu-Hc)qh!84iUw!BQ)mL7Z@Ldi0LAG52lsFI-l!07~p|6Ke-ofb6D$IQL^iX z2wU$U{aXiA%C{z=m@H%+{HFD0Y8+@U|L^4%1I`xO0l}wM??94z_gCew=}3>%-RovQ z&wf)+C})^xxF~`Gh9uAP^LSkx&jEau;|xA@p1>snH?~DR1(AoBGM93LWc(0Dq0{XK z@nmj25WS)wJ#jS(+I(CWMs%`ZKD!Z;_F0{pO1K4v5QbS<*b|YM2tM{&GzEl)*uLzN#gTkRGFYu#gr(@V-O%~i9B}{13`NFMk!hsspoc^=@ z;W3MAM(VJs!wsh;PQy1w(EXJzp!BUJTxiX;X7$qrHx5Y||L=^rdj&z8cT@X4&8gH^ z+8KZ@6MLyw-A1&e6x+SrJV*23f1K7u%~$Gq24N=5?R_~wTDx;#Du&QEEH(StVW~RB z@xDyV71v?1s`ltTL%Ssn0iXW8N0!O2ocn>gq=9{5HV%*DfGE~2DiOYUQ5w6!GzfI2 zY52NYu*!;|?}6lVW6`Nohn(EKN}sKqJ46ACCpcgp3rkAGU*4M!IKRYf7cW+6^VQM0 zHkK}^H+9E=sHui@7L}Oe`p|cy{L~BjiU)DxJQP?h?qen}KY)q^_2^?_ z2>@f5?w14pxXl(NnDItdz~;j1(anZ0Iws5UY(y6< zqX=-`(Q*z8;#-~8=U6ic!?RBP+s;!b^7>|ppJZ_{H>{sbU=O74@)BPezqUnPL!?r( z;*NhsEB*Qa05gCuf*`~WD+qbx**>;$lAx!IEbc3bRS&32)*dFZIGd8#6wA63#mJmEb(e>E@b#l(t7iyZ%TNUj+=&(FYQhM&mWr z14}un!`qxWf{%dGhI!<>hSRl9`P~DGtJTu&4*Y_=FFeO}-kqN@qly)h&56I?TR?;K zT3YQ%BkARP8-=fqa!no}q3!aLdVLLl;&LR?vSbKUmIk|EHVLO&-lNg|FXir9Uq!lIC9QgjWTIL< z5UOC-tyo1+HW(!H?a@T8nYaFz*!3l=eRkY1$14)4W&B~CUSm2%5gQ}Wvb{3cEcix2 zT#5StW%Bb%Z}eq{5rU!6kEq<_bi|Qq)Y)Fv96$AQOJPSmoPyq($h}}jD*f`jtgv;l zp7G^-N@Y~ho_h^?2sT39*sUM2tFg>ao^z{E4@NDOGTtZbU|#Ll*&0szwOQ(QHBviwm(@M0GP{GD4{L6R&1uHlHwE!el77#HzEC(36!X1;~zxt z_dhGVujB@OQ5U2L^2+J&0C-3#9CjP7=mNMvTK`X(16bn4j>_4na=Sv~`jxMUHM{a- zyYIOcJ=T0|*Sd!w+8u+NWJlT|!B%0OM}ywJBVG8#&)oJFM0b@)@&dTOa4U2ju~I4f9fScnkcZC zh2)_`6eQv5VZu!{Ge>X%#`SA6? zNH_m<{D-rw!4$#xQUJ}6yjA8mDPfT|;)^gn#nvXYZMTbtv#B7sfcfT|6!|M2QZ{g} ziv7{CH7S0U5QSS;=jMu!%|qcsc-Y&ZPHjzTlbK8hLS~pMzELF-n=?jDSd=97W$E~U zy0vHcEBo9GCuK{4H<1$fBktgQ_2Tn`m}8zlnL7co6!gvmM<@oYbEaXmq=3?Q^7lKl zhJ(r;PUW0^BV~96h0-=Kx3R9lTLF9*=ZxIsCTR6iEp75xO>z*g` zUN#)8bc58e$^W7cC^}6{%zyP-Cok8V`Uj2nm^XtO`p=HR6U4r5>$65>8~W6o0j-r2 zaHz$>CdrUS%EVENeE*Wjo`HPz{$;uPVM_WpzO*{{`}USG;nPh@c&-kci7;cyjmSNf z{7G-xH9d4bfYMbsolLjWLk5jdAAg?OIxW@dbgb1^DM zzoTdN*&s!w{?mQdEusC-*7m;UUCLZ+DX6fd84n^L=_O@Din8L zEmI;2&zs^t+5@H_Eikt7ltIDTNqC>TL-gx99qYuh#<$W6TAJ8wDi);+l>ar=z?~^h z=_Mz_%nt+f`VzbfZl6=FA;2YC_^m_$@R%$lfdtos(tqwJYyR1Jsdsti2b)>mSEl5< z4$Dt8G}wVb6i<#yJN+i;JfuvQ+I3v~5pBk6N3Kd`%|(@1+_$U3BE@H{*n|&de$8_4#M23+9W=tLu%gLkpE)wV-63tlb(Q?tH*?@pSFUB4z;0-~ z<@i;}jvW-N;-Vnf;(l>ClRZVk=C$UfR&3?qzXhA+)V}f!hcx_>KUfg#u!-2}5+`jX z`9~O?clYvg?k3J+Sf6#PeOGuh>)EB~`ST$WceAdcbhWu9#1Dx^Zc$yzW;QeWMS>XMtJ~dOlDVo#Wm9}u=8*mCOzbCL%f8f%}WsAR?7P> z!6DlNkL2&Dk@YqE^cdc|%k;aig0mS`zN66o&OX=FO{F@O%%v<}7OiAuHG$>OKMF+~ zmeURqdIxH%JyIZ)n>_pHBcH$j;ZR`q^^i-s-9+2cEXj|jIxHtmuCU+Jk)BF-Oj~<@ z-R}8mt1zGYY2};z-n!8-32S_$V*E!#h8iK&R3^v<&5*%tvkmMf^{4 z5GBkH@!CUo29%{&lisdCVf}8cL42{H{ZnlO91m70&0GpXyQypMm}s?UUhk{9oOdsu z>j^6B#O8FU7*nZ-E!RD!WR-JH=g4qS1mLctHyF!i?$S!V{tD0YH6IvfKFUA`pNBzu8$_kSRc9sC@RTP@mz%)J>I zJP^YWCEKPtSUGppv{VMe+$-&XQs{ah7chfIipsa}t47H|@hg9yS;s>YwUOuCv*Ln7 zpK>m%f4P!Yyh*Q1Heh~|{A43fbJu}$j~}+$h+>PZFxY2bguKOQ;YY||F1!;DqQ3~H zD`^v$b@_)*+M3VIN{%&+H+NWJY?QKCjE*mgQ}2u2o0lP6AmESqB8*S%y6~&--I{2I zEPFyQuCs*}&cC5r#hP_#J0Vgb0RuXxX%YmSB%<3Y@6m+E(HCY+B0&bLb87RrWv8?F zAp;V4g|6<^`_v^BY~>4^jg2q1LLJiKfRS6$t*x$EuW-w)*;eG9(Bp!q7FfY;U-&+? z+hU7kV(5};d30Nu2Pqqc*ePE3dx0qG2S*Cl)xop#zu!L-4V`Tj7QtWekxZV2Zj~FR z!yph!TZC}hQ!Ifz^I}o?d~bMqs3=Hwsor1BR31{8s2fZ z%*DrcC1~*r@;&u3vXL0Hh4z8y6u|(>xa8c>uc-Sc*cQhpBA(IT!yOVuuTU-v@z+-Q2U`~0< zzFMetHtD+1DVVGqR&wG=gvK;`I|#Ya2V-W6z65mFlbhHp@gg3nCIpw4=tH;{P^@SxCKeIeCiMvV(logwQ)MmfYt)SP~6nN8;pH%z|7NvSh zhibZ8l0(R|e~8#{ogJvj+lR{hB1j8*D0O68D~@g}I1t|P_~mBc025S!zgCX*dCTMp zc``xWh=+J&98I{3r2tEp9cEQI8)Pj25)m^)2IfUNMIlC~Sl2XYb{M&ZJq`AiRw`PC zEB#n3zEMA;uNq|Fl{XLP3VRZZD4GgsJcCnleUj+O83bhh6zj;|HguPy9vK?s9u)ft zdI`DxgSNx};jBgO zUpaE%bU}Op@}c9hN?IDeeuv@ZdoA9I6~rc&q&8`L%d7k$13{nK@fDrIaxLf#gD+dW z)pHD(UM_XItc8-dxHwyV=dg?dVXl>o5k~!BjYxem|5^T&!(B4%se+ZrHGxK(OEO~! z>7C@{C$0<4CLHma+ufwWiM&TA3~gmJCmY2sm&30^djdz#sZSbN7{kPZAI(N96iBMY zd}O*`DwQeDF~G*AwgmN+>mqkbeTN`#U#pT>Uh5*+-lErtKP6jCz#}R0l3~Gl3Kh8f zB`p2eavcGcfWE{Fw{j=|8;+U94XS#E_y|pE;sTHdM3+t80)&dXx@s*+b)pd7K7VZzF{@xTR;M(RpuK-XqdI`qum9ijPz) z_XzDD?dX6ah2kHL6{1uJ-@n2ZWA0ZYmmwKmIj@$T&E0I2J$d`h8T2+{&Oizc^QJmQ zEDByaX%y4X@+#-4{%9m&euT8t+sLDO{jB=6M(lR^CHC9*$$=>+L<_)Bu}KfvFcb5> z_mVT~-Xjq`r}aOZd>AvCni{zZCK%0~I7KD>{mL3Ihk%P$jpj4~s!z_Hk;WPvN^u9T zKSWaX>5m088(*q->%QoL>R*0{0*iH+jb=SmO?rSuXk8v`)>HGv7=qXz6nHxFZydkG zZbW%q`gk_tvkW7C^>F%f5vQId=bw^GW6PQ@O37JRWUbi&RwayUO4c|DHS)6%;Rdc& z>*mI|)F|?a{6j5EB#f*^nyPmm(t7#-sbk^FXsZnGMmtf&Sj=?`;IGRtBc60M!4snV zTE%;#tEOB%F1x#sy8C0doly;P2N3gh zDhZSx8*Oq}WiW2Z`CTPUY-IK9-v$IqT;u6_I^eg{zuB@Mak8C;Z+m|pOkO-#BTNu^ zWaQIPwT6=$e1SVw7OKOi_@^G!^7Jay z^ll=O0xk-zB%#`{1CJX;^n@$d)T zUbn0U|q~xn^qFuS>G08o9PC4DqF*y0R2|x(JNA8xU*na0EPiN9LgSAfTUQ#0n z=1XL&z{F~Z4D`%!iwNK7SclAHRF{TYv`T^4_=M*&0x#&-gN`t3Vq-^Y zj8>kGG%&RJU}rA7UXo>oPS}-$zR6PC!lY`Rs|syaNhiiqY{fShYyTgPr)pC;M;_gC z?TFsY4ei=1NYZibN+c=31C$7Zq|QYohlexSY3Y#V{+^Au^oxwb)OtBTf8+WE^)sBR zxh@1H%P%}+O}~U}pB$lIP8(`{aXry7P_Md+jdWR%49vTo;&a4JsMVr&lW9fW8S}%%M<+Hi-J254v25LPbVP^RA`AR>f-=@P>r&SZxXWvVrbs7+4 z!$g676voO9z`I}85^$b;ywH_)tAMx)fBHA8;6;gPVIqAoPKex0nR4xviL$c9{hJ=H zrz}Pxr2PodRomIm8n$@GWCrBPB&M+J8>k0 zTWe`XmQ#}llU!}Tk{^zgSM5I5q@xb$Y0VXoyk^Mp33TvmD&;wq-}ixiJ1D^cf_2aB zVd*ERjGk`3T3b^WG(63;`HNMPsRISkU!8EiGd*a+^N--ogg4y2V=m8%)%lMX&&uUu z<$9WagQJ=8OB+gp^CZLBI1Xnp{b$PZs)RyL=Z2YsAhB$_<3t39o769iZmh*3IQA_{ z#H|KH+a%AW0IXn`qK02GnSV^EH2_8)1r59t5kXmN`X4kXN`zv3`MoEMw_J(oCv(|C zC)p&mH@(izdM-KC_*z2EQ|vrMNN(|dsM*tF^>#Yq;=%L`ijrhOScZH%dFj3K{~QM0 z-(KsBJiHzX;8I*BQdh`)G&vJGDO+t=3$~w9p^H8e^)_~;omiPQ8gTzVjJ;JrTV2$y z3zQZsTBNuahXRG-S}eF1cPJ7Zf`&^xUnXGw{ z%r)1VV~+8T=gmNG$ZPekEo`Ul&>=trR_CgBKFufsbQ6XRuDgmyvNh7(tVy2CSV%*4~OA$F0?+<613PxImJv8$~ z5UK~NBiqrj?Juve7r`luA?fm#N!R-A;SifTL2&o#o*0+|ZzknXk2!;nCY$vb>CN(4CytjkQ-l%}@Es zj)ecSe)8xiLp9sJcW{vibP2X4dMB}ec8zX^OstzJtRTKFy`03ai4#B&x~+>Nw^Q8F z^vJNH6I+jz!4D$v&&Ik&{lD`@q{BB!4B3p!d(zcUu6-_oPpf` zp=PD>yV;DMx{UBF25cW}z|D%x3n^C?fW3DI=y04Y&yoQ|MaXoPoL!U3-S(?GA$d9K zi8kqJ`&7|~Ik95;!0Ym?9-=Dkt1Rh0E@3DX?%djnOs@JOOe|Ye zH-`MU4(-UJ-Zr7BSYw(&$0dSuu@A+;@p86YEl&ewo5c)xbu~vgx4({KPTuu}ub)n5 z)OB^hPbaPg8@r1>JePgYkTsCAH%$!d7wy6>BLbguhO51jhhIQ95c zXeHjQ0v(+N;LX3pisXJ;1&rG-G32r{>e%&dgm2QV3*K8STeVclG<{ByT1KF=eTxWS zkA|1|=c5YO$5tTpCRoK*t@+AMuRkJ64gXa8b6eIq4{ECziELWnn|}osHO`VT7@Ni+ zs`S!o(is961)^o)A zz_I{RCtG%L7FC`G0gX}9WDLL(P*hU5`Eu5DpfZg=*b2YeT`ox+UmsHPb%~Xee=z^vjl%V5V2eUra;2!)Kfo z9kdPB!+^;(X6}`M0(j-etQNWzE}0z0y*yfu5eFg`@bB2*z>7!sipX9qMbF=djMFMC z#C%7ZL*Ic1W3-ON2+TUl-QH0E*>an1SA$(#n6b6l=RcMor~xl5_#c5z%%wu(UE{`% zrHgTSD+ZeO{h_vjAJdKKmQa){4oFbj>P_edzhhXPHyl4;MKsnO@3-7l+oD&OZ-dOylhb zV%TBFwf>qJsX1a(31ELv8Wr#zcLIN~%zDHN-c(W?$u;W9<>X+cRa}z$t#Eu=)-hFK zRcogs?gX|Ocm&bXw7+Gf?InODv}jH`Do~1sFDeJhwjlSu6E0h9sMN1?31rYnB%)gm z1|J&^Nss|I6x+H!Y^i4=LFr{lt)XJBY-alSH=5Z;XuJP6#lV z7LnjN4JNE=Qd$j&WFnOL8AX!eU#jV`Gc!spG~YV&ao!+a-`w~#c|Z-*sHKln zXf)tkNxfGl+(AwAQm0}vW6j25sP=szO7QOdm|N-#sVlfj4{vQ(Y84*J=3eC3OW@y7 z=w(0H@P>SpC&obOv~sQHx$Tj7s7?(rjv8IMia?_wE1`yg&GqZb=zN_B0)&-lA3Dq_ zrk3HKL15C%KfrPHO=~n1l~tHiD7C~sCyR=Hjfg;1Q>QU6UD*CFU90O`4JKpBG!6o( zEcQ;Ca2*Iq)T6idr0WS&7?4As#L%kp|znaYTC6rH!HaK$_nqg7^n(`Tvh$v zS$TP4!u1o`X=(ItTxHN-cSDV3^NYF=9H#BVI-B1b_IbGo0;%yO*y$83SC9Cm0u_OR zSQS8rV1QJPdr*EZ&6!Z1D;moi$2q9oUDeW;r#a3yyy+@h&*XX)0}xilms|xYNzvx7 zq6D5OMXAm61tMwGxN6rd>x?y;xcL1Hj zr9eU1!f8oc&-(!&N9_!dmIg;>62kO*(Oq61kp@n^OpTh>iyKkOWdTU(1F?(d$Z@ri z>7Q90otwL7(nEb3Ez{G8j>VuY`UF=*Z<7u7^tC!URX>4rdsa+AM89v{bTwlopmu#! z-{4)yGSQ~Hmn79RpuMxbp0yWBR&M*sR(f36SnVcPqw54Y7Wc^^%cU*?|NeaDcovl7 z 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≫MMXkmac`_J7uq-y9j^tp)D?EH6=C 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

&f?l?^UCeUK0kIv0WZCxjE?s$&EaUiKR8Zy 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 E0SS=mSO5S3 diff --git a/web/src/assets/logo-mini-full.svg b/web/src/assets/logo-mini-full.svg deleted file mode 100644 index 4f02fdd..0000000 --- a/web/src/assets/logo-mini-full.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - \ No newline at end of file diff --git a/web/src/assets/logo-mini.svg b/web/src/assets/logo-mini.svg deleted file mode 100644 index 305bee5..0000000 --- a/web/src/assets/logo-mini.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - \ No newline at end of file diff --git a/web/src/assets/logo-normal.svg b/web/src/assets/logo-normal.svg deleted file mode 100644 index 29d9f3f..0000000 --- a/web/src/assets/logo-normal.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - \ No newline at end of file diff --git a/web/src/assets/logo-small.svg b/web/src/assets/logo-small.svg deleted file mode 100644 index d80894a..0000000 --- a/web/src/assets/logo-small.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - \ No newline at end of file diff --git a/web/src/components/AdminOnly.astro b/web/src/components/AdminOnly.astro deleted file mode 100644 index 03f8727..0000000 --- a/web/src/components/AdminOnly.astro +++ /dev/null @@ -1,11 +0,0 @@ ---- -import type { AstroChildren } from '../lib/astro' - -type Props = { - children: AstroChildren -} - -// ---- - -{!!Astro.locals.user?.admin && } diff --git a/web/src/components/BadgeSmall.astro b/web/src/components/BadgeSmall.astro deleted file mode 100644 index a38a144..0000000 --- a/web/src/components/BadgeSmall.astro +++ /dev/null @@ -1,162 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' -import { tv, type VariantProps } from 'tailwind-variants' - -import type { Polymorphic } from 'astro/types' - -const badge = tv({ - slots: { - base: 'inline-flex h-4 items-center justify-center gap-0.75 rounded-full px-1.25 text-[10px] font-medium', - icon: 'size-3 shrink-0', - text: 'mx-0.25 overflow-hidden text-ellipsis whitespace-nowrap', - }, - variants: { - color: { - red: '', - orange: '', - amber: '', - yellow: '', - lime: '', - green: '', - emerald: '', - teal: '', - cyan: '', - sky: '', - blue: '', - indigo: '', - violet: '', - purple: '', - fuchsia: '', - pink: '', - rose: '', - slate: '', - gray: '', - zinc: '', - neutral: '', - stone: '', - white: '', - black: '', - }, - variant: { - solid: '', - faded: '', - }, - }, - compoundVariants: [ - // Red - { color: 'red', variant: 'solid', class: { base: 'bg-red-500 text-white' } }, - { color: 'red', variant: 'faded', class: { base: 'bg-red-500/30 text-red-300' } }, - // Orange - { color: 'orange', variant: 'solid', class: { base: 'bg-orange-500 text-white' } }, - { color: 'orange', variant: 'faded', class: { base: 'bg-orange-500/30 text-orange-300' } }, - // Amber - { color: 'amber', variant: 'solid', class: { base: 'bg-amber-500 text-black' } }, - { color: 'amber', variant: 'faded', class: { base: 'bg-amber-500/30 text-amber-300' } }, - // Yellow - { color: 'yellow', variant: 'solid', class: { base: 'bg-yellow-500 text-black' } }, - { color: 'yellow', variant: 'faded', class: { base: 'bg-yellow-500/30 text-yellow-300' } }, - // Lime - { color: 'lime', variant: 'solid', class: { base: 'bg-lime-500 text-black' } }, - { color: 'lime', variant: 'faded', class: { base: 'bg-lime-500/30 text-lime-300' } }, - // Green - { color: 'green', variant: 'solid', class: { base: 'bg-green-500 text-black' } }, - { color: 'green', variant: 'faded', class: { base: 'bg-green-500/30 text-green-300' } }, - // Emerald - { color: 'emerald', variant: 'solid', class: { base: 'bg-emerald-500 text-white' } }, - { color: 'emerald', variant: 'faded', class: { base: 'bg-emerald-500/30 text-emerald-300' } }, - // Teal - { color: 'teal', variant: 'solid', class: { base: 'bg-teal-500 text-white' } }, - { color: 'teal', variant: 'faded', class: { base: 'bg-teal-500/30 text-teal-300' } }, - // Cyan - { color: 'cyan', variant: 'solid', class: { base: 'bg-cyan-500 text-white' } }, - { color: 'cyan', variant: 'faded', class: { base: 'bg-cyan-500/30 text-cyan-300' } }, - // Sky - { color: 'sky', variant: 'solid', class: { base: 'bg-sky-500 text-white' } }, - { color: 'sky', variant: 'faded', class: { base: 'bg-sky-500/30 text-sky-300' } }, - // Blue - { color: 'blue', variant: 'solid', class: { base: 'bg-blue-500 text-white' } }, - { color: 'blue', variant: 'faded', class: { base: 'bg-blue-500/30 text-blue-300' } }, - // Indigo - { color: 'indigo', variant: 'solid', class: { base: 'bg-indigo-500 text-white' } }, - { color: 'indigo', variant: 'faded', class: { base: 'bg-indigo-500/30 text-indigo-300' } }, - // Violet - { color: 'violet', variant: 'solid', class: { base: 'bg-violet-500 text-white' } }, - { color: 'violet', variant: 'faded', class: { base: 'bg-violet-500/30 text-violet-300' } }, - // Purple - { color: 'purple', variant: 'solid', class: { base: 'bg-purple-500 text-white' } }, - { color: 'purple', variant: 'faded', class: { base: 'bg-purple-500/30 text-purple-300' } }, - // Fuchsia - { color: 'fuchsia', variant: 'solid', class: { base: 'bg-fuchsia-500 text-white' } }, - { color: 'fuchsia', variant: 'faded', class: { base: 'bg-fuchsia-500/30 text-fuchsia-300' } }, - // Pink - { color: 'pink', variant: 'solid', class: { base: 'bg-pink-500 text-white' } }, - { color: 'pink', variant: 'faded', class: { base: 'bg-pink-500/30 text-pink-300' } }, - // Rose - { color: 'rose', variant: 'solid', class: { base: 'bg-rose-500 text-white' } }, - { color: 'rose', variant: 'faded', class: { base: 'bg-rose-500/30 text-rose-300' } }, - // Slate - { color: 'slate', variant: 'solid', class: { base: 'bg-slate-500 text-white' } }, - { color: 'slate', variant: 'faded', class: { base: 'bg-slate-500/30 text-slate-300' } }, - // Gray - { color: 'gray', variant: 'solid', class: { base: 'bg-gray-500 text-white' } }, - { color: 'gray', variant: 'faded', class: { base: 'bg-gray-500/30 text-gray-300' } }, - // Zinc - { color: 'zinc', variant: 'solid', class: { base: 'bg-zinc-500 text-white' } }, - { color: 'zinc', variant: 'faded', class: { base: 'bg-zinc-500/30 text-zinc-300' } }, - // Neutral - { color: 'neutral', variant: 'solid', class: { base: 'bg-neutral-500 text-white' } }, - { color: 'neutral', variant: 'faded', class: { base: 'bg-neutral-500/30 text-neutral-300' } }, - // Stone - { color: 'stone', variant: 'solid', class: { base: 'bg-stone-500 text-white' } }, - { color: 'stone', variant: 'faded', class: { base: 'bg-stone-500/30 text-stone-300' } }, - // White - { color: 'white', variant: 'solid', class: { base: 'bg-white text-black' } }, - { color: 'white', variant: 'faded', class: { base: 'bg-white-500/30 text-white-300' } }, - // Black - { color: 'black', variant: 'solid', class: { base: 'bg-black text-white' } }, - { color: 'black', variant: 'faded', class: { base: 'bg-black-500/30 text-black-300' } }, - ], - defaultVariants: { - color: 'gray', - variant: 'solid', - }, -}) - -type Props = Polymorphic< - VariantProps & { - as: Tag - icon?: string - text: string - inlineIcon?: boolean - classNames?: { - icon?: string - text?: string - } - } -> - -const { - as: Tag = 'div', - icon: iconName, - text: textContent, - inlineIcon, - classNames, - - color, - variant, - - class: className, - ...props -} = Astro.props - -const { base, icon: iconSlot, text: textSlot } = badge({ color, variant }) ---- - - - { - !!iconName && ( - - ) - } - {textContent} - diff --git a/web/src/components/BadgeStandard.astro b/web/src/components/BadgeStandard.astro deleted file mode 100644 index b1e2032..0000000 --- a/web/src/components/BadgeStandard.astro +++ /dev/null @@ -1,27 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' - -import { cn } from '../lib/cn' - -import type { Polymorphic } from 'astro/types' - -type Props = Polymorphic<{ - as: Tag - icon: string - text: string - inlineIcon?: boolean -}> - -const { icon, text, class: className, inlineIcon, as: Tag = 'div', ...divProps } = Astro.props ---- - - - - {text} - diff --git a/web/src/components/BaseHead.astro b/web/src/components/BaseHead.astro deleted file mode 100644 index efe2bf2..0000000 --- a/web/src/components/BaseHead.astro +++ /dev/null @@ -1,133 +0,0 @@ ---- -import LoadingIndicator from 'astro-loading-indicator/component' -import { Schema } from 'astro-seo-schema' -import { ClientRouter } from 'astro:transitions' - -import { isNotArray } from '../lib/arrays' -import { DEPLOYMENT_MODE } from '../lib/envVariables' - -import HtmxScript from './HtmxScript.astro' -import { makeOgImageUrl } from './OgImage' -import TailwindJsPluggin from './TailwindJsPluggin.astro' - -import type { ComponentProps } from 'astro/types' -import type { WithContext, BreadcrumbList, ListItem } from 'schema-dts' - -export type BreadcrumArray = [ - ...{ - name: string - url: string - }[], - { - name: string - url?: string - }, -] - -type Props = { - pageTitle: string - /** - * Whether to enable htmx. - * - * @default false - */ - htmx?: boolean - /** - * Page meta description - * - * @default 'KYCnot.me helps you find services without KYC for better privacy and control over your data.' - */ - description?: string - /** - * Open Graph image. - * - If `string` is provided, it will be used as the image URL. - * - If `{ template: string, ...props }` is provided, it will be used to generate an Open Graph image based on the template. - */ - ogImage?: Parameters[0] - - schemas?: ComponentProps['item'][] - - breadcrumbs?: BreadcrumArray | BreadcrumArray[] -} - -const { - pageTitle, - htmx = false, - description = 'KYCnot.me helps you find services without KYC for better privacy and control over your data.', - ogImage, - schemas, - breadcrumbs, -} = Astro.props - -const breadcrumbLists = breadcrumbs?.every(Array.isArray) - ? (breadcrumbs as BreadcrumArray[]) - : breadcrumbs?.every(isNotArray) - ? [breadcrumbs] - : [] - -const modeName = DEPLOYMENT_MODE === 'production' ? '' : DEPLOYMENT_MODE === 'staging' ? 'PRE' : 'DEV' -const fullTitle = `${pageTitle} | KYCnot.me ${modeName}` -const ogImageUrl = makeOgImageUrl(ogImage, Astro.url) ---- - - - - -{DEPLOYMENT_MODE === 'development' && } -{DEPLOYMENT_MODE === 'staging' && } - - - - -{fullTitle} - - - - - - - -{!!ogImageUrl && } - - - - - - -{!!ogImageUrl && } - - - - - - - - - -{htmx && } - - -{schemas?.map((item) => )} - - -{ - breadcrumbLists.map((breadcrumbList) => ( - - ({ - '@type': 'ListItem', - position: index + 1, - name: item.name, - item: item.url ? new URL(item.url, Astro.url).href : undefined, - }) satisfies ListItem - ), - } satisfies WithContext - } - /> - )) -} diff --git a/web/src/components/Button.astro b/web/src/components/Button.astro deleted file mode 100644 index 6a8f95a..0000000 --- a/web/src/components/Button.astro +++ /dev/null @@ -1,176 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' -import { tv, type VariantProps } from 'tailwind-variants' - -import type { HTMLAttributes, Polymorphic } from 'astro/types' - -type Props = Polymorphic< - Required, Tag extends 'label' ? 'for' : never>> & - VariantProps & { - as: Tag - label?: string - icon?: string - endIcon?: string - classNames?: { - label?: string - icon?: string - endIcon?: string - } - dataAstroReload?: boolean - children?: never - disabled?: boolean - } -> - -export type ButtonProps = Props - -const button = tv({ - slots: { - base: 'inline-flex items-center justify-center gap-2 rounded-lg border transition-colors duration-100 focus-visible:ring-2 focus-visible:ring-current focus-visible:ring-offset-2 focus-visible:ring-offset-black focus-visible:outline-hidden', - icon: 'size-4 shrink-0', - label: 'text-left whitespace-nowrap', - endIcon: 'size-4 shrink-0', - }, - variants: { - size: { - sm: { - base: 'h-8 px-3 text-sm', - icon: 'size-4', - endIcon: 'size-4', - }, - md: { - base: 'h-9 px-4 text-sm', - icon: 'size-4', - endIcon: 'size-4', - label: 'font-medium', - }, - lg: { - base: 'h-10 px-5 text-base', - icon: 'size-5', - endIcon: 'size-5', - label: 'font-bold tracking-wider uppercase', - }, - }, - color: { - black: { - base: 'border-night-500 bg-night-800 hover:bg-night-900 hover:text-day-200 focus-visible:bg-night-500 text-white/50 focus-visible:text-white focus-visible:ring-white', - }, - white: { - base: 'border-day-300 bg-day-100 hover:bg-day-200 text-black focus-visible:ring-green-500', - }, - gray: { - base: 'border-day-500 bg-day-400 hover:bg-day-500 text-black focus-visible:ring-white', - }, - success: { - base: 'border-green-600 bg-green-500 text-black hover:bg-green-600', - }, - error: { - base: 'border-red-600 bg-red-500 text-white hover:bg-red-600', - }, - warning: { - base: 'border-yellow-600 bg-yellow-500 text-white hover:bg-yellow-600', - }, - info: { - base: 'border-blue-600 bg-blue-500 text-white hover:bg-blue-600', - }, - }, - shadow: { - true: { - base: 'shadow-lg', - }, - }, - disabled: { - true: { - base: 'cursor-not-allowed', - }, - }, - }, - compoundVariants: [ - { - color: 'black', - shadow: true, - class: 'shadow-black/30', - }, - { - color: 'white', - shadow: true, - class: 'shadow-white/30', - }, - { - color: 'gray', - shadow: true, - class: 'shadow-day-500/30', - }, - { - color: 'success', - shadow: true, - class: 'shadow-green-500/30', - }, - { - color: 'error', - shadow: true, - class: 'shadow-red-500/30', - }, - { - color: 'warning', - shadow: true, - class: 'shadow-yellow-500/30', - }, - { - color: 'info', - shadow: true, - class: 'shadow-blue-500/30', - }, - ], - defaultVariants: { - size: 'md', - color: 'black', - shadow: false, - disabled: false, - }, -}) - -const { - as: Tag = 'button' as 'a' | 'button' | 'label', - label, - icon, - endIcon, - size, - color, - shadow, - class: className, - classNames, - role, - dataAstroReload, - disabled, - ...htmlProps -} = Astro.props - -const { - base, - icon: iconSlot, - label: labelSlot, - endIcon: endIconSlot, -} = button({ size, color, shadow, disabled }) - -const ActualTag = disabled && Tag === 'a' ? 'span' : Tag ---- - - - {!!icon && } - {!!label && {label}} - { - !!endIcon && ( - - {endIcon} - - ) - } - diff --git a/web/src/components/Captcha.astro b/web/src/components/Captcha.astro deleted file mode 100644 index a9cb8fa..0000000 --- a/web/src/components/Captcha.astro +++ /dev/null @@ -1,80 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' -import { isInputError, type ActionAccept, type ActionClient } from 'astro:actions' -import { Image } from 'astro:assets' - -import { CAPTCHA_LENGTH, generateCaptcha } from '../lib/captcha' -import { cn } from '../lib/cn' - -import type { HTMLAttributes } from 'astro/types' -import type { z } from 'astro:content' - -type Props< - TAccept extends ActionAccept, - TInputSchema extends z.ZodType, - TAction extends ActionClient, -> = HTMLAttributes<'div'> & { - action: TAction -} - -const { class: className, action: formAction, autofocus, ...htmlProps } = Astro.props - -const result = Astro.getActionResult(formAction) -const inputErrors = isInputError(result?.error) ? result.error.fields : {} - -const captcha = generateCaptcha() ---- - -{/* eslint-disable astro/jsx-a11y/no-autofocus */} - -

-

- This page requires a visual CAPTCHA to ensure you are a human. If you are unable to complete the CAPTCHA, - please email us for assistance. contact@kycnot.me -

- -
- - - - - -
- - { - inputErrors['captcha-value'] && ( -

{inputErrors['captcha-value'].join(', ')}

- ) - } - - -
diff --git a/web/src/components/Chat.astro b/web/src/components/Chat.astro deleted file mode 100644 index 63ec14d..0000000 --- a/web/src/components/Chat.astro +++ /dev/null @@ -1,86 +0,0 @@ ---- -import { isInputError } from 'astro:actions' - -import { SUGGESTION_MESSAGE_CONTENT_MAX_LENGTH } from '../actions/serviceSuggestion' -import Button from '../components/Button.astro' -import Tooltip from '../components/Tooltip.astro' -import { cn } from '../lib/cn' -import { baseInputClassNames } from '../lib/formInputs' - -import ChatMessages, { type ChatMessage } from './ChatMessages.astro' - -import type { ActionInputNoFormData, AnyAction } from '../lib/astroActions' -import type { HTMLAttributes } from 'astro/types' - -export type Props = - HTMLAttributes<'section'> & { - messages: ChatMessage[] - title?: string - userId: number | null - action: TAction - formData?: TAction extends AnyAction - ? ActionInputNoFormData extends Record - ? Omit, 'content'> - : ActionInputNoFormData - : undefined - } - -const { messages, title, userId, action, formData, class: className, ...htmlProps } = Astro.props - -const result = action ? Astro.getActionResult(action) : undefined -const inputErrors = isInputError(result?.error) ? result.error.fields : {} ---- - -
- {!!title &&

{title}

} - - - - { - !!action && ( - <> -
- {typeof formData === 'object' && - formData !== null && - Object.entries(formData).map(([key, value]) => ( - - ))} - - diff --git a/web/src/components/InputWrapper.astro b/web/src/components/InputWrapper.astro deleted file mode 100644 index 463b8af..0000000 --- a/web/src/components/InputWrapper.astro +++ /dev/null @@ -1,74 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' -import { Markdown } from 'astro-remote' - -import { cn } from '../lib/cn' - -import type { AstroChildren } from '../lib/astro' -import type { MarkdownString } from '../lib/markdown' -import type { HTMLAttributes } from 'astro/types' - -type Props = HTMLAttributes<'div'> & { - children: AstroChildren - label: string - name: string - description?: MarkdownString - descriptionLabel?: string - required?: HTMLAttributes<'input'>['required'] - error?: string[] | string - icon?: string - inputId?: string -} - -const { - label, - name, - description, - descriptionLabel, - required, - error, - icon, - class: className, - inputId, - ...htmlProps -} = Astro.props - -const hasError = !!error && error.length > 0 ---- - -
-
- - {icon && } - {required && '*'} - - { - !!descriptionLabel && ( - {descriptionLabel} - ) - } -
- - - - { - hasError && - (typeof error === 'string' ? ( -

{error}

- ) : ( -
    - {error.map((e) => ( -
  • {e}
  • - ))} -
- )) - } - - { - !!description && ( -
- -
- ) - } -
diff --git a/web/src/components/KarmaUnlocksTable.astro b/web/src/components/KarmaUnlocksTable.astro deleted file mode 100644 index 933f4b7..0000000 --- a/web/src/components/KarmaUnlocksTable.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import { orderBy } from 'lodash-es' - -import { karmaUnlocks } from '../constants/karmaUnlocks' - -const karmaUnlocksSorted = orderBy(karmaUnlocks, [ - ({ karma }) => (karma >= 0 ? 1 : 2), - ({ karma }) => Math.abs(karma), - 'id', -]) ---- - - - - - - - - - - { - karmaUnlocksSorted.map((unlock) => ( - - - - - )) - } - -
KarmaUnlock
{unlock.karma.toLocaleString()}{unlock.name}
diff --git a/web/src/components/Logo.astro b/web/src/components/Logo.astro deleted file mode 100644 index 24820ed..0000000 --- a/web/src/components/Logo.astro +++ /dev/null @@ -1,65 +0,0 @@ ---- -import type { HTMLAttributes } from 'astro/types' - -type Props = Omit, 'viewBox' | 'xmlns'> & { - variant?: 'mini-full' | 'mini' | 'normal' | 'small' -} - -const { variant = 'normal', ...htmlProps } = Astro.props ---- - -{ - variant === 'normal' && ( - - - - ) -} - -{ - variant === 'small' && ( - - - - ) -} - -{ - variant === 'mini' && ( - - - - ) -} - -{ - variant === 'mini-full' && ( - - - - ) -} diff --git a/web/src/components/OgImage.tsx b/web/src/components/OgImage.tsx deleted file mode 100644 index dae2906..0000000 --- a/web/src/components/OgImage.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import fs from 'node:fs' -import path from 'node:path' - -import { ImageResponse } from '@vercel/og' - -import { urlWithParams } from '../lib/urls' - -import type { Prettify } from 'ts-essentials' - -export type GenericOgImageProps = Partial> - -////////////////////////////////////////////////////// -// NOTE // -// Use this website to create and preview templates // -// https://og-playground.vercel.app/ // -////////////////////////////////////////////////////// - -const defaultOptions = { - width: 1200, - height: 630, - fonts: [ - { - name: 'Inter', - weight: 400, - style: 'normal', - data: fs.readFileSync( - path.resolve( - process.cwd(), - 'node_modules', - '@fontsource', - 'inter', - 'files', - 'inter-latin-400-normal.woff' - ) - ), - }, - { - name: 'Inter', - weight: 700, - style: 'normal', - data: fs.readFileSync( - path.resolve( - process.cwd(), - 'node_modules', - '@fontsource', - 'inter', - 'files', - 'inter-latin-700-normal.woff' - ) - ), - }, - ], -} as const satisfies ConstructorParameters[1] - -export const ogImageTemplates = { - default: () => { - return new ImageResponse( - ( -
- - - -
- ), - defaultOptions - ) - }, - generic: ({ title }: { title?: string }) => { - return new ImageResponse( - ( -
- {title} - - - -
- ), - defaultOptions - ) - }, -} as const satisfies Record ImageResponse | null> - -type OgImageTemplate = keyof typeof ogImageTemplates -type OgImageProps = Parameters<(typeof ogImageTemplates)[T]>[0] -// eslint-disable-next-line @typescript-eslint/sort-type-constituents -export type OgImageAllTemplatesWithGenericProps = { template: OgImageTemplate } & GenericOgImageProps - -export type OgImageAllTemplatesWithProps = Prettify< - { - // eslint-disable-next-line @typescript-eslint/sort-type-constituents - [K in OgImageTemplate]: { template: K } & Omit, 'template'> - }[OgImageTemplate] -> - -export function makeOgImageUrl( - ogImage: OgImageAllTemplatesWithProps | string | undefined, - baseUrl: URL | string -) { - return typeof ogImage === 'string' - ? new URL(ogImage, baseUrl).href - : urlWithParams(new URL('/ogimage.png', baseUrl), ogImage ?? {}) -} diff --git a/web/src/components/Pagination.astro b/web/src/components/Pagination.astro deleted file mode 100644 index 48d920e..0000000 --- a/web/src/components/Pagination.astro +++ /dev/null @@ -1,134 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' - -import { cn } from '../lib/cn' -import { createPageUrl } from '../lib/urls' - -import type { HTMLAttributes } from 'astro/types' - -type Props = HTMLAttributes<'nav'> & { - currentPage: number - totalPages: number - currentUrl?: URL | string - sortSeed?: string -} - -const { - currentPage, - totalPages, - currentUrl = Astro.url, - sortSeed, - class: className, - ...navProps -} = Astro.props - -const prevPage = currentPage > 1 ? currentPage - 1 : null -const nextPage = currentPage < totalPages ? currentPage + 1 : null - -const getVisiblePages = () => { - const pages: (number | '...')[] = [] - - if (totalPages <= 9) { - return Array.from({ length: totalPages }, (_, i) => i + 1) - } - - // Always show first page - pages.push(1) - - if (currentPage > 4) { - pages.push('...') - } - - // Calculate range around current page - let rangeStart = Math.max(2, currentPage - 2) - let rangeEnd = Math.min(totalPages - 1, currentPage + 2) - - // Adjust range if at the start or end - if (currentPage <= 4) { - rangeEnd = 6 - } - if (currentPage >= totalPages - 3) { - rangeStart = totalPages - 5 - } - - // Add range numbers - for (let i = rangeStart; i <= rangeEnd; i++) { - pages.push(i) - } - - if (currentPage < totalPages - 3) { - pages.push('...') - } - - // Always show last page - pages.push(totalPages) - - return pages -} -const PrevTag = prevPage ? 'a' : 'span' -const NextTag = nextPage ? 'a' : 'span' ---- - - diff --git a/web/src/components/PillsRadioGroup.astro b/web/src/components/PillsRadioGroup.astro deleted file mode 100644 index a41a37a..0000000 --- a/web/src/components/PillsRadioGroup.astro +++ /dev/null @@ -1,41 +0,0 @@ ---- -import { cn } from '../lib/cn' - -import type { HTMLAttributes } from 'astro/types' - -type Props = HTMLAttributes<'div'> & { - name: string - options: { - value: HTMLAttributes<'input'>['value'] - label: string - }[] - selectedValue?: string | null -} - -const { name, options, selectedValue, class: className, ...rest } = Astro.props ---- - -
- { - options.map((option) => ( - - )) - } -
diff --git a/web/src/components/ScoreGauge.astro b/web/src/components/ScoreGauge.astro deleted file mode 100644 index 568b4c1..0000000 --- a/web/src/components/ScoreGauge.astro +++ /dev/null @@ -1,175 +0,0 @@ ---- -import { Schema } from 'astro-seo-schema' - -import { cn } from '../lib/cn' -import { interpolate } from '../lib/numbers' -import { KYCNOTME_SCHEMA_MINI } from '../lib/schema' -import { transformCase } from '../lib/strings' - -import type { HTMLAttributes } from 'astro/types' -import type { Review, WithContext } from 'schema-dts' - -export type Props = HTMLAttributes<'div'> & { - score: number - label: string - total?: number - itemReviewedId?: string -} - -const { score, label, total = 100, class: className, itemReviewedId, ...htmlProps } = Astro.props - -const progress = total === 0 ? 0 : Math.min(Math.max(score / total, 0), 1) - -function makeScoreInfo(score: number, total: number) { - const formattedScore = Math.round(score).toLocaleString() - const angle = interpolate(progress, -100, 100) - const n = score / total - - if (n > 1) return { text: 'Excellent', step: 5, formattedScore, angle: 100 } - if (n >= 0.9 && n <= 1) return { text: 'Excellent', step: 5, formattedScore, angle } - if (n >= 0.8 && n < 0.9) return { text: 'Very Good', step: 5, formattedScore, angle } - if (n >= 0.6 && n < 0.8) return { text: 'Good', step: 4, formattedScore, angle } - if (n >= 0.45 && n < 0.6) return { text: 'Average', step: 3, formattedScore, angle } - if (n >= 0.4 && n < 0.45) return { text: 'Average', step: 3, formattedScore, angle: angle + 5 } - if (n >= 0.2 && n < 0.4) return { text: 'Bad', step: 2, formattedScore, angle: angle + 5 } - if (n >= 0.1 && n < 0.2) return { text: 'Very Bad', step: 1, formattedScore, angle } - if (n >= 0 && n < 0.1) return { text: 'Terrible', step: 1, formattedScore, angle } - if (n < 0) return { text: 'Terrible', step: 1, formattedScore, angle: -100 } - - return { text: '', step: undefined, formattedScore, angle: undefined } -} - -const { text, step, angle, formattedScore } = makeScoreInfo(score, total) ---- - -{ - !!itemReviewedId && ( - - } - /> - ) -} - -
-
2, - })} - > - {formattedScore} -
- -
- {label} -
- - {text} - - -
diff --git a/web/src/components/ScoreSquare.astro b/web/src/components/ScoreSquare.astro deleted file mode 100644 index 84b0a5e..0000000 --- a/web/src/components/ScoreSquare.astro +++ /dev/null @@ -1,106 +0,0 @@ ---- -import { Schema } from 'astro-seo-schema' - -import { cn } from '../lib/cn' -import { KYCNOTME_SCHEMA_MINI } from '../lib/schema' -import { transformCase } from '../lib/strings' - -import type { HTMLAttributes } from 'astro/types' - -export type Props = HTMLAttributes<'div'> & { - score: number - label: string - total?: number - itemReviewedId?: string -} - -const { score, label, total = 10, class: className, itemReviewedId, ...htmlProps } = Astro.props - -export function makeOverallScoreInfo(score: number, total = 10) { - const classNamesByColor = { - red: 'bg-score-1 text-black', - orange: 'bg-score-2 text-black', - yellow: 'bg-score-3 text-black', - blue: 'bg-score-4 text-black', - green: 'bg-score-5 text-black', - } as const satisfies Record - - const formattedScore = Math.round(score).toLocaleString() - const n = score / total - - if (n > 1) return { text: '', classNameBg: classNamesByColor.green, formattedScore } - if (n >= 0.9 && n <= 1) return { text: 'Excellent', classNameBg: classNamesByColor.green, formattedScore } - if (n >= 0.8 && n < 0.9) return { text: 'Very Good', classNameBg: classNamesByColor.blue, formattedScore } - if (n >= 0.7 && n < 0.8) return { text: 'Good', classNameBg: classNamesByColor.blue, formattedScore } - if (n >= 0.6 && n < 0.7) return { text: 'Okay', classNameBg: classNamesByColor.yellow, formattedScore } - if (n >= 0.5 && n < 0.6) { - return { text: 'Acceptable', classNameBg: classNamesByColor.yellow, formattedScore } - } - if (n >= 0.4 && n < 0.5) return { text: 'Bad', classNameBg: classNamesByColor.orange, formattedScore } - if (n >= 0.3 && n < 0.4) return { text: 'Very Bad', classNameBg: classNamesByColor.orange, formattedScore } - if (n >= 0.2 && n < 0.3) return { text: 'Really Bad', classNameBg: classNamesByColor.red, formattedScore } - if (n >= 0 && n < 0.2) return { text: 'Terrible', classNameBg: classNamesByColor.red, formattedScore } - return { text: '', classNameBg: undefined, formattedScore } -} - -const { text, classNameBg, formattedScore } = makeOverallScoreInfo(score, total) ---- - -
- { - !!itemReviewedId && ( - - ) - } - - -
2, - } - )} - > - - {formattedScore} - -
- -
- {label} -
- - {text} -
diff --git a/web/src/components/ServiceCard.astro b/web/src/components/ServiceCard.astro deleted file mode 100644 index d4ca06f..0000000 --- a/web/src/components/ServiceCard.astro +++ /dev/null @@ -1,155 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' -import { Image } from 'astro:assets' - -import defaultImage from '../assets/fallback-service-image.jpg' -import { currencies } from '../constants/currencies' -import { verificationStatusesByValue } from '../constants/verificationStatus' -import { cn } from '../lib/cn' -import { transformCase } from '../lib/strings' - -import { makeOverallScoreInfo } from './ScoreSquare.astro' -import Tooltip from './Tooltip.astro' - -import type { Prisma } from '@prisma/client' -import type { HTMLAttributes } from 'astro/types' - -type Props = HTMLAttributes<'a'> & { - inlineIcons?: boolean - withoutLink?: boolean - service: Prisma.ServiceGetPayload<{ - select: { - name: true - slug: true - description: true - overallScore: true - kycLevel: true - imageUrl: true - verificationStatus: true - acceptedCurrencies: true - categories: { - select: { - name: true - icon: true - } - } - } - }> -} - -const { - inlineIcons = false, - service: { - name = 'Unnamed Service', - slug, - description, - overallScore, - - kycLevel, - imageUrl, - categories, - verificationStatus, - acceptedCurrencies, - }, - class: className, - withoutLink = false, - ...aProps -} = Astro.props - -const statusIcon = { - ...verificationStatusesByValue, - APPROVED: undefined, -}[verificationStatus] - -const Element = withoutLink ? 'div' : 'a' - -const overallScoreInfo = makeOverallScoreInfo(overallScore) ---- - - - -
- {name - -
-

- {name}{ - statusIcon && ( - - - - ) - } -

-
-
- { - categories.map((category) => ( - - - {category.name} - - )) - } -
-
-
- -
-

- {description} -

-
- -
- - {overallScoreInfo.formattedScore} - - - - KYC  {kycLevel.toLocaleString()} - - -
- { - currencies.map((currency) => { - const isAccepted = acceptedCurrencies.includes(currency.id) - - return ( - - - - ) - }) - } -
-
-
diff --git a/web/src/components/ServiceFiltersPill.astro b/web/src/components/ServiceFiltersPill.astro deleted file mode 100644 index 04fd1d6..0000000 --- a/web/src/components/ServiceFiltersPill.astro +++ /dev/null @@ -1,36 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' - -import { cn } from '../lib/cn' - -import type { HTMLAttributes } from 'astro/types' - -type Props = HTMLAttributes<'a'> & { - text: string - searchParamName: string - searchParamValue?: string - icon?: string - iconClass?: string -} - -const { text, searchParamName, searchParamValue, icon, iconClass, class: className, ...aProps } = Astro.props - -const makeUrlWithoutFilter = (filter: string, value?: string) => { - const url = new URL(Astro.url) - url.searchParams.delete(filter, value) - return url.toString() -} ---- - - - {icon && } - {text} - - diff --git a/web/src/components/ServiceLinkButton.astro b/web/src/components/ServiceLinkButton.astro deleted file mode 100644 index 2b77040..0000000 --- a/web/src/components/ServiceLinkButton.astro +++ /dev/null @@ -1,132 +0,0 @@ ---- -import { z } from 'astro/zod' -import { Icon } from 'astro-icon/components' - -import { networksBySlug } from '../constants/networks' -import { cn } from '../lib/cn' - -import type { HTMLAttributes } from 'astro/types' - -type Props = Omit, 'href' | 'rel' | 'target'> & { - url: string - referral: string | null - enableMinWidth?: boolean -} - -const { url: baseUrl, referral, class: className, enableMinWidth = false, ...htmlProps } = Astro.props - -function makeLink(url: string, referral: string | null) { - const hostname = new URL(url).hostname - const urlWithReferral = url + (referral ?? '') - - const onionMatch = /^(?:https?:\/\/)?(.{0,10}).*?(.{0,10})(\.onion)$/.exec(hostname) - if (onionMatch) { - return { - type: 'onion' as const, - url: urlWithReferral, - textBits: onionMatch.length - ? [ - { - style: 'normal' as const, - text: onionMatch[1] ?? '', - }, - { - style: 'irrelevant' as const, - text: '...', - }, - { - style: 'normal' as const, - text: onionMatch[2] ?? '', - }, - { - style: 'irrelevant' as const, - text: onionMatch[3] ?? '', - }, - ] - : [ - { - style: 'normal' as const, - text: hostname, - }, - ], - icon: networksBySlug.onion.icon, - } - } - - const i2pMatch = /^(?:https?:\/\/)?(.{0,10}).*?(.{0,8})((?:\.b32)?\.i2p)$/.exec(hostname) - if (i2pMatch) { - return { - type: 'i2p' as const, - url: urlWithReferral, - textBits: i2pMatch.length - ? [ - { - style: 'normal' as const, - text: i2pMatch[1] ?? '', - }, - { - style: 'irrelevant' as const, - text: '...', - }, - { - style: 'normal' as const, - text: i2pMatch[2] ?? '', - }, - { - style: 'irrelevant' as const, - text: i2pMatch[3] ?? '', - }, - ] - : [ - { - style: 'normal' as const, - text: hostname, - }, - ], - icon: networksBySlug.i2p.icon, - } - } - - return { - type: 'clearnet' as const, - url: urlWithReferral, - textBits: [ - { - style: 'normal' as const, - text: hostname.replace(/^www\./, ''), - }, - ], - icon: networksBySlug.clearnet.icon, - } -} - -const link = makeLink(baseUrl, referral) - -if (!z.string().url().safeParse(link.url).success) { - console.error(`Invalid service URL with referral: ${link.url}`) -} ---- - - - - - { - link.textBits.map((textBit) => ( - {textBit.text} - )) - } - - - diff --git a/web/src/components/ServicesFilters.astro b/web/src/components/ServicesFilters.astro deleted file mode 100644 index 058a77a..0000000 --- a/web/src/components/ServicesFilters.astro +++ /dev/null @@ -1,497 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' - -import { kycLevels } from '../constants/kycLevels' -import { cn } from '../lib/cn' -import { type ServicesFiltersObject, type ServicesFiltersOptions } from '../pages/index.astro' - -import Button from './Button.astro' -import PillsRadioGroup from './PillsRadioGroup.astro' -import { makeOverallScoreInfo } from './ScoreSquare.astro' -import Tooltip from './Tooltip.astro' - -import type { HTMLAttributes } from 'astro/types' - -export type Props = HTMLAttributes<'form'> & { - filters: ServicesFiltersObject - hasDefaultFilters: boolean - options: ServicesFiltersOptions - searchResultsId: string - showFiltersId: string -} - -const { - filters, - hasDefaultFilters, - options, - searchResultsId, - showFiltersId, - class: className, - ...formProps -} = Astro.props ---- - - verification.default) - .map((verification) => verification.slug)} - {...formProps} - class={cn('', className)} -> -
-

FILTERS

- Clear all -
- - -
- - - - -

- - Ties randomly sorted -

-
- - -
- - - - -
- - -
- Type - -
    - { - options.categories?.map((category) => ( -
  • - -
  • - )) - } -
- { - options.categories.filter((category) => category.showAlways).length < options.categories.length && ( - <> - - - - ) - } -
- - -
- Verification -
- { - options.verification.map((verification) => ( - - )) - } -
-
- - -
-
- Currencies - -
-
- { - options.currencies.map((currency) => ( - - )) - } -
-
- - -
- Networks -
- { - options.network.map((network) => ( - - )) - } -
-
- - -
- - - -
- -
-
- { - kycLevels.map((level) => ( - - {level.value} - - - )) - } -
-
- - -
- - - -
- -
-
- - - - 1 - - - 2 - - - 3 - - - 4 - -
-
- - -
-
- Attributes - -
- { - options.attributesByCategory.map(({ category, attributes }) => ( -
- {category} - -
    - {attributes.map((attribute) => { - const inputName = `attr-${attribute.id}` as const - const yesId = `attr-${attribute.id}=yes` as const - const noId = `attr-${attribute.id}=no` as const - const emptyId = `attr-${attribute.id}=empty` as const - const isPositive = attribute.type === 'GOOD' || attribute.type === 'INFO' - - return ( -
  • -
    - - {attribute.title} ({attribute._count?.services}) - - - - - - - - -
    -
  • - ) - })} -
- {attributes.filter((attribute) => attribute.showAlways).length < attributes.length && ( - <> - - - - )} -
- )) - } -
- - -
- - - -
- -
-
- { - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((score) => { - const info = makeOverallScoreInfo(score) - return ( - - {score.toLocaleString()} - - ) - }) - } -
-
- - - -
-
- - - diff --git a/web/src/components/ServicesSearchResults.astro b/web/src/components/ServicesSearchResults.astro deleted file mode 100644 index e6a8e74..0000000 --- a/web/src/components/ServicesSearchResults.astro +++ /dev/null @@ -1,141 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' - -import { cn } from '../lib/cn' -import { pluralize } from '../lib/pluralize' -import { createPageUrl } from '../lib/urls' - -import Button from './Button.astro' -import ServiceCard from './ServiceCard.astro' - -import type { ServicesFiltersObject } from '../pages/index.astro' -import type { ComponentProps, HTMLAttributes } from 'astro/types' - -type Props = HTMLAttributes<'div'> & { - hasDefaultFilters?: boolean - services: ComponentProps['service'][] | undefined - currentPage?: number - total: number - pageSize: number - sortSeed?: string - filters: ServicesFiltersObject - hadToIncludeCommunityContributed: boolean -} - -const { - services, - hasDefaultFilters = false, - currentPage = 1, - total, - pageSize, - sortSeed, - class: className, - filters, - hadToIncludeCommunityContributed, - ...divProps -} = Astro.props - -const hasScams = filters.verification.includes('VERIFICATION_FAILED') -const hasCommunityContributed = - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - filters.verification.includes('COMMUNITY_CONTRIBUTED') || hadToIncludeCommunityContributed - -const totalPages = Math.ceil(total / pageSize) || 1 ---- - -
-
- - {total.toLocaleString()} - {pluralize('result', total)} - - - - Loading... - - -
- - { - hasScams && hasCommunityContributed && ( -
- - - Showing SCAM and unverified community-contributed services. - {hadToIncludeCommunityContributed && 'Because there were no other results.'} -
- ) - } - - { - hasScams && !hasCommunityContributed && ( -
- - Showing SCAM services! -
- ) - } - - { - !hasScams && hasCommunityContributed && ( -
- - - {hadToIncludeCommunityContributed - ? 'Showing unverified community-contributed services, because there were no other results. Some might be scams.' - : 'Showing unverified community-contributed services, some might be scams.'} -
- ) - } - - { - !services || services.length === 0 ? ( -
- -

No services found

-

Try adjusting your filters to find more services

- - Clear filters - -
- ) : ( - <> -
- {services.map((service, i) => ( - - ))} -
- -
-
- - Loading more services... -
-
- - ) - } -
diff --git a/web/src/components/SortArrowIcon.astro b/web/src/components/SortArrowIcon.astro deleted file mode 100644 index f376ae3..0000000 --- a/web/src/components/SortArrowIcon.astro +++ /dev/null @@ -1,25 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' - -import { cn } from '../lib/cn' - -type Props = { - active: boolean - sortOrder: 'asc' | 'desc' | null | undefined - class?: string -} - -const { active, sortOrder, class: className }: Props = Astro.props ---- - -{ - active && sortOrder ? ( - sortOrder === 'asc' ? ( - - ) : ( - - ) - ) : ( - - ) -} diff --git a/web/src/components/TailwindJsPluggin.astro b/web/src/components/TailwindJsPluggin.astro deleted file mode 100644 index 585f3d2..0000000 --- a/web/src/components/TailwindJsPluggin.astro +++ /dev/null @@ -1,11 +0,0 @@ ---- - ---- - - diff --git a/web/src/components/TimeFormatted.astro b/web/src/components/TimeFormatted.astro deleted file mode 100644 index 55dd983..0000000 --- a/web/src/components/TimeFormatted.astro +++ /dev/null @@ -1,16 +0,0 @@ ---- -import { omit } from 'lodash-es' - -import { formatDateShort, type FormatDateShortOptions } from '../lib/timeAgo' - -import type { HTMLAttributes } from 'astro/types' - -type Props = FormatDateShortOptions & - Omit, keyof FormatDateShortOptions | 'datetime'> & { - date: Date - } - -const { date, ...props } = Astro.props ---- - - diff --git a/web/src/components/Tooltip.astro b/web/src/components/Tooltip.astro deleted file mode 100644 index eac5576..0000000 --- a/web/src/components/Tooltip.astro +++ /dev/null @@ -1,93 +0,0 @@ ---- -import { cn } from '../lib/cn' - -import type { AstroChildren, AstroComponent, PolymorphicComponent } from '../lib/astro' -import type { HTMLTag } from 'astro/types' - -type Props = PolymorphicComponent & { - children: AstroChildren - text: string - classNames?: { - tooltip?: string - } - color?: 'black' | 'white' | 'zinc-700' - position?: 'bottom' | 'left' | 'right' | 'top' - enabled?: boolean -} - -const { - as: Component = 'span', - text, - classNames, - class: className, - color = 'zinc-700', - position = 'top', - enabled = true, - ...htmlProps -} = Astro.props ---- - - - - { - enabled && ( - - ) - } - diff --git a/web/src/components/VerificationWarningBanner.astro b/web/src/components/VerificationWarningBanner.astro deleted file mode 100644 index 2f3115e..0000000 --- a/web/src/components/VerificationWarningBanner.astro +++ /dev/null @@ -1,69 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' -import { differenceInDays, isPast } from 'date-fns' - -import { verificationStatusesByValue } from '../constants/verificationStatus' -import { cn } from '../lib/cn' - -import TimeFormatted from './TimeFormatted.astro' - -import type { Prisma } from '@prisma/client' - -const RECENTLY_ADDED_DAYS = 7 - -type Props = { - service: Prisma.ServiceGetPayload<{ - select: { - verificationStatus: true - verificationProofMd: true - verificationSummary: true - listedAt: true - createdAt: true - } - }> -} - -const { service } = Astro.props - -const listedDate = service.listedAt ?? service.createdAt -const wasRecentlyAdded = isPast(listedDate) && differenceInDays(new Date(), listedDate) < RECENTLY_ADDED_DAYS ---- - -{ - service.verificationStatus === 'VERIFICATION_FAILED' ? ( -
-

- - This service is a SCAM! - {!!service.verificationProofMd && ( - - Proof - - )} -

- {!!service.verificationSummary && ( -
{service.verificationSummary}
- )} -
- ) : service.verificationStatus === 'COMMUNITY_CONTRIBUTED' ? ( -
- - Community-contributed. Information not reviewed. -
- ) : wasRecentlyAdded ? ( -
- This service was {service.listedAt === null ? 'added ' : 'listed '}{' '} - - {service.verificationStatus !== 'VERIFICATION_SUCCESS' && ' and it is not verified'}. Proceed with - caution. -
- ) : service.verificationStatus !== 'VERIFICATION_SUCCESS' ? ( -
- - Basic checks passed, but not fully verified. -
- ) : null -} diff --git a/web/src/constants/accountStatusChange.ts b/web/src/constants/accountStatusChange.ts deleted file mode 100644 index 7c2eaee..0000000 --- a/web/src/constants/accountStatusChange.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { makeHelpersForOptions } from '../lib/makeHelpersForOptions' -import { transformCase } from '../lib/strings' - -import type { AccountStatusChange } from '@prisma/client' - -type AccountStatusChangeInfo = { - value: T - label: string - notificationTitle: string -} - -export const { - dataArray: accountStatusChanges, - dataObject: accountStatusChangesById, - getFn: getAccountStatusChangeInfo, - zodEnumById: accountStatusChangesZodEnumById, -} = makeHelpersForOptions( - 'value', - (value): AccountStatusChangeInfo => ({ - value, - label: value ? transformCase(value.replaceAll('_', ' '), 'title') : String(value), - notificationTitle: value ? transformCase(value.replaceAll('_', ' '), 'title') : String(value), - }), - [ - { - value: 'ADMIN_TRUE', - label: 'Admin role granted', - notificationTitle: 'Admin role granted', - }, - { - value: 'ADMIN_FALSE', - label: 'Admin role revoked', - notificationTitle: 'Admin role revoked', - }, - { - value: 'VERIFIED_TRUE', - label: 'Account verified', - notificationTitle: 'Your account is now verified', - }, - { - value: 'VERIFIED_FALSE', - label: 'Account unverified', - notificationTitle: 'Your account is no longer verified', - }, - { - value: 'VERIFIER_TRUE', - label: 'Verifier role granted', - notificationTitle: 'Verifier role granted', - }, - { - value: 'VERIFIER_FALSE', - label: 'Verifier role revoked', - notificationTitle: 'Verifier role revoked', - }, - { - value: 'SPAMMER_TRUE', - label: 'Banned', - notificationTitle: 'Your account has been banned', - }, - { - value: 'SPAMMER_FALSE', - label: 'Unbanned', - notificationTitle: 'Your account is no longer banned', - }, - ] as const satisfies AccountStatusChangeInfo[] -) - -export type AccountStatusChangeType = (typeof accountStatusChanges)[number]['value'] diff --git a/web/src/constants/attributeCategories.ts b/web/src/constants/attributeCategories.ts deleted file mode 100644 index c6c135a..0000000 --- a/web/src/constants/attributeCategories.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { makeHelpersForOptions } from '../lib/makeHelpersForOptions' -import { transformCase } from '../lib/strings' - -import type { AttributeCategory } from '@prisma/client' - -type AttributeCategoryInfo = { - value: T - slug: string - label: string - icon: string - classNames: { - icon: string - } - order: number -} - -export const { - dataArray: attributeCategories, - dataObject: attributeCategoriesById, - getFn: getAttributeCategoryInfo, - getFnSlug: getAttributeCategoryInfoBySlug, - zodEnumBySlug: attributeCategoriesZodEnumBySlug, - zodEnumById: attributeCategoriesZodEnumById, - keyToSlug: attributeCategoryIdToSlug, - slugToKey: attributeCategorySlugToId, -} = makeHelpersForOptions( - 'value', - (value): AttributeCategoryInfo => ({ - value, - slug: value ? value.toLowerCase() : '', - label: value ? transformCase(value, 'title') : String(value), - icon: 'ri:shield-fill', - classNames: { - icon: 'text-current/60', - }, - order: Infinity, - }), - [ - { - value: 'PRIVACY', - slug: 'privacy', - label: 'Privacy', - icon: 'ri:shield-user-fill', - classNames: { - icon: 'text-blue-500', - }, - order: 1, - }, - { - value: 'TRUST', - slug: 'trust', - label: 'Trust', - icon: 'ri:shield-check-fill', - classNames: { - icon: 'text-green-500', - }, - order: 2, - }, - ] as const satisfies AttributeCategoryInfo[] -) diff --git a/web/src/constants/attributeTypes.ts b/web/src/constants/attributeTypes.ts deleted file mode 100644 index d317947..0000000 --- a/web/src/constants/attributeTypes.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { makeHelpersForOptions } from '../lib/makeHelpersForOptions' -import { transformCase } from '../lib/strings' - -import type { AttributeType } from '@prisma/client' - -type AttributeTypeInfo = { - value: T - slug: string - label: string - icon: string - order: number - classNames: { - container: string - subcontainer: string - text: string - textLight: string - icon: string - button: string - } -} - -export const { - dataArray: attributeTypes, - dataObject: attributeTypesById, - getFn: getAttributeTypeInfo, - getFnSlug: getAttributeTypeInfoBySlug, - zodEnumBySlug: attributeTypesZodEnumBySlug, - zodEnumById: attributeTypesZodEnumById, - keyToSlug: attributeTypeIdToSlug, - slugToKey: attributeTypeSlugToId, -} = makeHelpersForOptions( - 'value', - (value): AttributeTypeInfo => ({ - value, - slug: value ? value.toLowerCase() : '', - label: value ? transformCase(value, 'title') : String(value), - icon: 'ri:question-line', - order: Infinity, - classNames: { - container: 'bg-current/30', - subcontainer: 'bg-current/5 border-current/30', - text: 'text-current/60', - textLight: 'text-current/40', - icon: 'text-current/60', - button: 'bg-current/80 text-current/100 hover:bg-current/50', - }, - }), - [ - { - value: 'BAD', - slug: 'bad', - label: 'Bad', - icon: 'ri:close-line', - order: 1, - classNames: { - container: 'bg-red-600/30', - subcontainer: 'bg-red-600/5 border-red-600/30', - text: 'text-red-200', - textLight: 'text-red-100', - icon: 'text-red-400', - button: 'bg-red-200 text-red-900 hover:bg-red-50', - }, - }, - { - value: 'WARNING', - slug: 'warning', - label: 'Warning', - icon: 'ri:alert-line', - order: 2, - classNames: { - container: 'bg-yellow-600/30', - subcontainer: 'bg-yellow-600/5 border-yellow-600/30', - text: 'text-yellow-200', - textLight: 'text-amber-100', - icon: 'text-yellow-400', - button: 'bg-amber-100 text-amber-900 hover:bg-amber-50', - }, - }, - { - value: 'GOOD', - slug: 'good', - label: 'Good', - icon: 'ri:check-line', - order: 3, - classNames: { - container: 'bg-green-600/30', - subcontainer: 'bg-green-600/5 border-green-600/30', - text: 'text-green-200', - textLight: 'text-green-100', - icon: 'text-green-400', - button: 'bg-green-200 text-green-900 hover:bg-green-50', - }, - }, - { - value: 'INFO', - slug: 'info', - label: 'Info', - icon: 'ri:information-line', - order: 4, - classNames: { - container: 'bg-blue-600/30', - subcontainer: 'bg-blue-600/5 border-blue-600/30', - text: 'text-blue-200', - textLight: 'text-blue-100', - icon: 'text-blue-400', - button: 'bg-blue-200 text-blue-900 hover:bg-blue-50', - }, - }, - ] as const satisfies AttributeTypeInfo[] -) diff --git a/web/src/constants/characters.ts b/web/src/constants/characters.ts deleted file mode 100644 index 5035758..0000000 --- a/web/src/constants/characters.ts +++ /dev/null @@ -1,92 +0,0 @@ -export const SEARCH_PARAM_CHARACTERS_NO_ESCAPE = [ - 'a', - 'b', - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i', - 'j', - 'k', - 'l', - 'm', - 'n', - 'o', - 'p', - 'q', - 'r', - 's', - 't', - 'u', - 'v', - 'w', - 'x', - 'y', - 'z', - 'A', - 'B', - 'C', - 'D', - 'E', - 'F', - 'G', - 'H', - 'I', - 'J', - 'K', - 'L', - 'M', - 'N', - 'O', - 'P', - 'Q', - 'R', - 'S', - 'T', - 'U', - 'V', - 'W', - 'X', - 'Y', - 'Z', - '0', - '1', - '2', - '3', - '4', - '5', - '6', - '7', - '8', - '9', - '-', - '_', - '.', - '~', -] as const - -export const LOWERCASE_VOWEL_CHARACTERS = ['a', 'e', 'i', 'o', 'u'] as const -export const LOWERCASE_CONSONANT_CHARACTERS = [ - 'b', - 'c', - 'd', - 'f', - 'g', - 'h', - 'j', - 'k', - 'l', - 'm', - 'n', - 'p', - 'r', - 's', - 't', - 'v', - 'w', - 'y', - 'z', -] as const -export const DIGIT_CHARACTERS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] as const diff --git a/web/src/constants/commentStatus.ts b/web/src/constants/commentStatus.ts deleted file mode 100644 index 3c66f1c..0000000 --- a/web/src/constants/commentStatus.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { makeHelpersForOptions } from '../lib/makeHelpersForOptions' -import { transformCase } from '../lib/strings' - -import type { CommentStatus } from '@prisma/client' - -type CommentStatusInfo = { - id: T - icon: string - label: string - creativeWorkStatus: string | undefined -} - -export const { - dataArray: commentStatus, - dataObject: commentStatusById, - getFn: getCommentStatusInfo, -} = makeHelpersForOptions( - 'id', - (id): CommentStatusInfo => ({ - id, - icon: 'ri:question-line', - label: id ? transformCase(id, 'title') : String(id), - creativeWorkStatus: undefined, - }), - [ - { - id: 'PENDING', - icon: 'ri:question-line', - label: 'Pending', - creativeWorkStatus: 'Deleted', - }, - { - id: 'HUMAN_PENDING', - icon: 'ri:question-line', - label: 'Pending 2', - creativeWorkStatus: 'Deleted', - }, - { - id: 'VERIFIED', - icon: 'ri:check-line', - label: 'Verified', - creativeWorkStatus: 'Verified', - }, - { - id: 'REJECTED', - icon: 'ri:close-line', - label: 'Rejected', - creativeWorkStatus: 'Deleted', - }, - { - id: 'APPROVED', - icon: 'ri:check-line', - label: 'Approved', - creativeWorkStatus: 'Active', - }, - ] as const satisfies CommentStatusInfo[] -) diff --git a/web/src/constants/commentStatusChange.ts b/web/src/constants/commentStatusChange.ts deleted file mode 100644 index d28ae03..0000000 --- a/web/src/constants/commentStatusChange.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { makeHelpersForOptions } from '../lib/makeHelpersForOptions' -import { transformCase } from '../lib/strings' - -import type { CommentStatusChange } from '@prisma/client' - -type CommentStatusChangeInfo = { - value: T - label: string - notificationTitle: string -} - -export const { - dataArray: commentStatusChanges, - dataObject: commentStatusChangesById, - getFn: getCommentStatusChangeInfo, - zodEnumById: commentStatusChangesZodEnumById, -} = makeHelpersForOptions( - 'value', - (value): CommentStatusChangeInfo => ({ - value, - label: value ? transformCase(value.replaceAll('_', ' '), 'title') : String(value), - notificationTitle: value ? transformCase(value.replaceAll('_', ' '), 'title') : String(value), - }), - [ - { - value: 'MARKED_AS_SPAM', - label: 'Marked as spam', - notificationTitle: 'was marked as spam', - }, - { - value: 'UNMARKED_AS_SPAM', - label: 'Unmarked as spam', - notificationTitle: 'is no longer marked as spam', - }, - { - value: 'MARKED_FOR_ADMIN_REVIEW', - label: 'Marked for admin review', - notificationTitle: 'was marked for admin review', - }, - { - value: 'UNMARKED_FOR_ADMIN_REVIEW', - label: 'Unmarked for admin review', - notificationTitle: 'is no longer marked for admin review', - }, - { - value: 'STATUS_CHANGED_TO_APPROVED', - label: 'Approved', - notificationTitle: 'was approved', - }, - { - value: 'STATUS_CHANGED_TO_VERIFIED', - label: 'Verified', - notificationTitle: 'was verified', - }, - { - value: 'STATUS_CHANGED_TO_REJECTED', - label: 'Rejected', - notificationTitle: 'was rejected', - }, - { - value: 'STATUS_CHANGED_TO_PENDING', - label: 'Pending', - notificationTitle: 'is now pending', - }, - ] as const satisfies CommentStatusChangeInfo[] -) - -export type CommentStatusChangeType = (typeof commentStatusChanges)[number]['value'] diff --git a/web/src/constants/commentStatusFilters.ts b/web/src/constants/commentStatusFilters.ts deleted file mode 100644 index d6c41c8..0000000 --- a/web/src/constants/commentStatusFilters.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { makeHelpersForOptions } from '../lib/makeHelpersForOptions' -import { transformCase } from '../lib/strings' - -import type { Prisma } from '@prisma/client' - -type CommentStatusFilterInfo = { - value: T - label: string - whereClause: Prisma.CommentWhereInput - styles: { - filter: string - badge: string - } -} - -export const { - dataArray: commentStatusFilters, - dataObject: commentStatusFiltersById, - getFn: getCommentStatusFilterInfo, - zodEnumById: commentStatusFiltersZodEnum, -} = makeHelpersForOptions( - 'value', - (value): CommentStatusFilterInfo => ({ - value, - label: value ? transformCase(value, 'title') : String(value), - whereClause: {}, - styles: { - filter: 'border-zinc-700 transition-colors hover:border-green-500/50', - badge: '', - }, - }), - [ - { - label: 'All', - value: 'all', - whereClause: {}, - styles: { - filter: 'border-green-500 bg-green-500/20 text-green-400', - badge: '', - }, - }, - { - label: 'Pending', - value: 'pending', - whereClause: { - OR: [{ status: 'PENDING' }, { status: 'HUMAN_PENDING' }], - }, - styles: { - filter: 'border-blue-500 bg-blue-500/20 text-blue-400', - badge: 'rounded-sm bg-blue-500/20 px-2 py-0.5 text-[12px] font-medium text-blue-500', - }, - }, - { - label: 'Rejected', - value: 'rejected', - whereClause: { - status: 'REJECTED', - }, - styles: { - filter: 'border-red-500 bg-red-500/20 text-red-400', - badge: 'rounded-sm bg-red-500/20 px-2 py-0.5 text-[12px] font-medium text-red-500', - }, - }, - { - label: 'Suspicious', - value: 'suspicious', - whereClause: { - suspicious: true, - }, - styles: { - filter: 'border-red-500 bg-red-500/20 text-red-400', - badge: 'rounded-sm bg-red-500/20 px-2 py-0.5 text-[12px] font-medium text-red-500', - }, - }, - { - label: 'Verified', - value: 'verified', - whereClause: { - status: 'VERIFIED', - }, - styles: { - filter: 'border-blue-500 bg-blue-500/20 text-blue-400', - badge: 'rounded-sm bg-blue-500/20 px-2 py-0.5 text-[12px] font-medium text-blue-500', - }, - }, - { - label: 'Approved', - value: 'approved', - whereClause: { - status: 'APPROVED', - }, - styles: { - filter: 'border-green-500 bg-green-500/20 text-green-400', - badge: 'rounded-sm bg-green-500/20 px-2 py-0.5 text-[12px] font-medium text-green-500', - }, - }, - { - label: 'Needs Review', - value: 'needs-review', - whereClause: { - requiresAdminReview: true, - }, - styles: { - filter: 'border-yellow-500 bg-yellow-500/20 text-yellow-400', - badge: 'rounded-sm bg-yellow-500/20 px-2 py-0.5 text-[12px] font-medium text-yellow-500', - }, - }, - ] as const satisfies CommentStatusFilterInfo[] -) - -export type CommentStatusFilter = (typeof commentStatusFilters)[number]['value'] - -export function getCommentStatusFilterValue( - comment: Prisma.CommentGetPayload<{ - select: { - status: true - suspicious: true - requiresAdminReview: true - } - }> -): CommentStatusFilter { - if (comment.requiresAdminReview) return 'needs-review' - if (comment.suspicious) return 'suspicious' - - switch (comment.status) { - case 'PENDING': - case 'HUMAN_PENDING': { - return 'pending' - } - case 'VERIFIED': { - return 'verified' - } - case 'REJECTED': { - return 'rejected' - } - case 'APPROVED': { - return 'approved' - } - default: { - return 'all' - } - } -} diff --git a/web/src/constants/currencies.ts b/web/src/constants/currencies.ts deleted file mode 100644 index a31cb3e..0000000 --- a/web/src/constants/currencies.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { makeHelpersForOptions } from '../lib/makeHelpersForOptions' -import { transformCase } from '../lib/strings' - -import type { Currency } from '@prisma/client' - -type CurrencyInfo = { - id: T - icon: string - name: string - slug: string -} - -export const { - dataArray: currencies, - dataObject: currenciesById, - getFn: getCurrencyInfo, - getFnSlug: getCurrencyInfoBySlug, - zodEnumBySlug: currenciesZodEnumBySlug, - keyToSlug: currencyIdToSlug, - slugToKey: currencySlugToId, -} = makeHelpersForOptions( - 'id', - (id): CurrencyInfo => ({ - id, - icon: 'ri:question-line', - name: id ? transformCase(id, 'title') : String(id), - slug: id ? id.toLowerCase() : '', - }), - [ - { - id: 'MONERO', - icon: 'monero', - name: 'Monero', - slug: 'xmr', - }, - { - id: 'BITCOIN', - icon: 'bitcoin', - name: 'Bitcoin', - slug: 'btc', - }, - { - id: 'LIGHTNING', - icon: 'ri:flashlight-line', - name: 'Lightning', - slug: 'btc-ln', - }, - { - id: 'FIAT', - icon: 'credit-card', - name: 'Fiat', - slug: 'fiat', - }, - { - id: 'CASH', - icon: 'coins', - name: 'Cash', - slug: 'cash', - }, - ] as const satisfies CurrencyInfo[] -) diff --git a/web/src/constants/eventTypes.ts b/web/src/constants/eventTypes.ts deleted file mode 100644 index 7a5350d..0000000 --- a/web/src/constants/eventTypes.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { makeHelpersForOptions } from '../lib/makeHelpersForOptions' -import { transformCase } from '../lib/strings' - -import type { EventType } from '@prisma/client' - -type EventTypeInfo = { - id: T - slug: string - label: string - description: string - classNames: { - dot: string - } - icon: string -} - -export const { - dataArray: eventTypes, - dataObject: eventTypesById, - getFn: getEventTypeInfo, - getFnSlug: getEventTypeInfoBySlug, - zodEnumBySlug: eventTypesZodEnumBySlug, - zodEnumById: eventTypesZodEnumById, -} = makeHelpersForOptions( - 'id', - (id): EventTypeInfo => ({ - id, - slug: id ? id.toLowerCase() : '', - label: id ? transformCase(id, 'title') : String(id), - description: '', - classNames: { - dot: 'bg-zinc-700 text-zinc-300 ring-zinc-700/50', - }, - icon: 'ri:question-fill', - }), - [ - { - id: 'WARNING', - slug: 'warning', - label: 'Warning', - description: 'Potential issues that users should be aware of', - classNames: { - dot: 'bg-amber-900 text-amber-300 ring-amber-900/50', - }, - icon: 'ri:error-warning-fill', - }, - { - id: 'WARNING_SOLVED', - slug: 'warning-solved', - label: 'Warning Solved', - description: 'A previously reported warning has been solved', - classNames: { - dot: 'bg-green-900 text-green-300 ring-green-900/50', - }, - icon: 'ri:check-fill', - }, - { - id: 'ALERT', - slug: 'alert', - label: 'Alert', - description: 'Critical issues affecting service functionality', - classNames: { - dot: 'bg-red-900 text-red-300 ring-red-900/50', - }, - icon: 'ri:alert-fill', - }, - { - id: 'ALERT_SOLVED', - slug: 'alert-solved', - label: 'Alert Solved', - description: 'A previously reported alert has been solved', - classNames: { - dot: 'bg-green-900 text-green-300 ring-green-900/50', - }, - icon: 'ri:check-fill', - }, - { - id: 'INFO', - slug: 'info', - label: 'Information', - description: 'General information about the service', - classNames: { - dot: 'bg-blue-900 text-blue-300 ring-blue-900/50', - }, - icon: 'ri:information-fill', - }, - { - id: 'NORMAL', - slug: 'normal', - label: 'Normal', - description: 'Regular service update or announcement', - classNames: { - dot: 'bg-zinc-700 text-zinc-300 ring-zinc-700/50', - }, - icon: 'ri:notification-fill', - }, - { - id: 'UPDATE', - slug: 'update', - label: 'Update', - description: 'Service details were updated on kycnot.me', - classNames: { - dot: 'bg-sky-900 text-sky-300 ring-sky-900/50', - }, - icon: 'ri:pencil-fill', - }, - ] as const satisfies EventTypeInfo[] -) diff --git a/web/src/constants/karmaUnlocks.ts b/web/src/constants/karmaUnlocks.ts deleted file mode 100644 index 8d943d3..0000000 --- a/web/src/constants/karmaUnlocks.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { makeHelpersForOptions } from '../lib/makeHelpersForOptions' -import { transformCase } from '../lib/strings' - -export type KarmaUnlockInfo = { - id: T - name: string - verb: string - description: string - karma: number - icon: string -} - -export const { dataArray: karmaUnlocks, dataObject: karmaUnlocksById } = makeHelpersForOptions( - 'id', - (id): KarmaUnlockInfo => ({ - id, - name: id ? transformCase(id, 'title') : String(id), - description: id ? transformCase(id, 'sentence') : String(id), - karma: 0, - icon: 'ri:question-line', - verb: id ? transformCase(id, 'title') : String(id), - }), - [ - { - id: 'voteComments', - name: 'Vote on comments', - verb: 'vote on comments', - description: 'You can vote on comments', - karma: 20, - icon: 'ri:thumb-up-line', - }, - { - id: 'websiteLink', - name: 'Website link', - verb: 'add a website link', - description: 'You can add a website link to your profile', - karma: 175, - icon: 'ri:link', - }, - { - id: 'displayName', - name: 'Display name', - verb: 'have a display name', - description: 'You can change your display name', - karma: 150, - icon: 'ri:user-smile-line', - }, - { - id: 'profilePicture', - name: 'Profile picture', - verb: 'have a profile picture', - description: 'You can change your profile picture', - karma: 200, - icon: 'ri:image-line', - }, - { - id: 'highKarmaBadge', - name: 'High Karma badge', - verb: 'become a high karma user', - description: 'You are a high karma user', - karma: 500, - icon: 'ri:shield-star-line', - }, - { - id: 'negativeKarmaBadge', - name: 'Negative Karma badge', - verb: 'be a suspicious user', - description: 'You are a suspicious user', - karma: -10, - icon: 'ri:error-warning-line', - }, - { - id: 'untrustedBadge', - name: 'Untrusted badge', - verb: 'be an untrusted user', - description: 'You are an untrusted user', - karma: -30, - icon: 'ri:spam-2-line', - }, - { - id: 'commentsDisabled', - name: 'Comments disabled', - verb: 'cannot comment', - description: 'You cannot comment', - karma: -50, - icon: 'ri:forbid-line', - }, - ] as const satisfies KarmaUnlockInfo[] -) diff --git a/web/src/constants/kycLevels.ts b/web/src/constants/kycLevels.ts deleted file mode 100644 index 5f48e0c..0000000 --- a/web/src/constants/kycLevels.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { makeHelpersForOptions } from '../lib/makeHelpersForOptions' -import { parseIntWithFallback } from '../lib/numbers' -import { transformCase } from '../lib/strings' - -type KycLevelInfo = { - id: T - value: number - icon: string - name: string - description: string -} - -export const { - dataArray: kycLevels, - dataObject: kycLevelsById, - getFn: getKycLevelInfo, -} = makeHelpersForOptions( - 'id', - (id): KycLevelInfo => ({ - id, - value: parseIntWithFallback(id, 4), - icon: 'diamond-question', - name: `KYC ${id ? transformCase(id, 'title') : String(id)}`, - description: '', - }), - [ - { - id: '0', - value: 0, - icon: 'anonymous-mask', - name: 'Guaranteed no KYC', - description: 'Terms explicitly state KYC will never be requested.', - }, - { - id: '1', - value: 1, - icon: 'diamond-question', - name: 'No KYC mention', - description: 'No mention of current or future KYC requirements.', - }, - { - id: '2', - value: 2, - icon: 'handcuffs', - name: 'KYC on authorities request', - description: - 'No routine KYC, but may cooperate with authorities, block funds or implement future KYC requirements.', - }, - { - id: '3', - value: 3, - icon: 'gun', - name: 'Shotgun KYC', - description: 'May request KYC and block funds based on automated triggers.', - }, - { - id: '4', - value: 4, - icon: 'fingerprint-detailed', - name: 'Mandatory KYC', - description: 'Required for key features and can be required arbitrarily at any time.', - }, - ] as const satisfies KycLevelInfo<'0' | '1' | '2' | '3' | '4'>[] -) diff --git a/web/src/constants/networks.ts b/web/src/constants/networks.ts deleted file mode 100644 index 99338de..0000000 --- a/web/src/constants/networks.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { makeHelpersForOptions } from '../lib/makeHelpersForOptions' -import { transformCase } from '../lib/strings' - -type NetworkInfo = { - slug: T - icon: string - name: string -} - -export const { - dataArray: networks, - dataObject: networksBySlug, - getFn: getNetworkInfo, -} = makeHelpersForOptions( - 'slug', - (slug): NetworkInfo => ({ - slug, - icon: 'ri:global-line', - name: slug ? transformCase(slug, 'title') : String(slug), - }), - [ - { - slug: 'clearnet', - icon: 'ri:global-line', - name: 'Clearnet', - }, - { - slug: 'onion', - icon: 'onion', - name: 'Onion', - }, - { - slug: 'i2p', - icon: 'i2p', - name: 'I2P', - }, - ] as const satisfies NetworkInfo[] -) diff --git a/web/src/constants/notificationTypes.ts b/web/src/constants/notificationTypes.ts deleted file mode 100644 index bf6e67e..0000000 --- a/web/src/constants/notificationTypes.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { makeHelpersForOptions } from '../lib/makeHelpersForOptions' - -import type { NotificationType } from '@prisma/client' - -type NotificationTypeInfo = { - id: T - label: string - icon: string -} - -export const { - dataArray: notificationTypes, - dataObject: notificationTypeLabels, - getFn: getNotificationTypeInfo, -} = makeHelpersForOptions( - 'id', - (id): NotificationTypeInfo => ({ - id, - label: 'Notification', - icon: 'ri:notification-line', - }), - [ - { - id: 'COMMENT_STATUS_CHANGE', - label: 'Comment status changed', - icon: 'ri:chat-check-line', - }, - { - id: 'REPLY_COMMENT_CREATED', - label: 'New reply', - icon: 'ri:chat-4-line', - }, - { - id: 'ROOT_COMMENT_CREATED', - label: 'New comment/rating', - icon: 'ri:chat-4-line', - }, - { - id: 'SUGGESTION_MESSAGE', - label: 'New message in suggestion', - icon: 'ri:mail-line', - }, - { - id: 'SUGGESTION_STATUS_CHANGE', - label: 'Suggestion status changed', - icon: 'ri:lightbulb-line', - }, - // TODO: [KARMA_UNLOCK] Will be added later, when karma unloks are in the database, not in the code. - // { - // id: 'KARMA_UNLOCK', - // label: 'Karma unlock', - // icon: 'ri:award-line', - // }, - { - id: 'ACCOUNT_STATUS_CHANGE', - label: 'Change in account status', - icon: 'ri:user-settings-line', - }, - { - id: 'EVENT_CREATED', - label: 'New event', - icon: 'ri:calendar-event-line', - }, - { - id: 'SERVICE_VERIFICATION_STATUS_CHANGE', - label: 'Service verification changed', - icon: 'ri:verified-badge-line', - }, - ] as const satisfies NotificationTypeInfo[] -) diff --git a/web/src/constants/project.ts b/web/src/constants/project.ts deleted file mode 100644 index 8fadc87..0000000 --- a/web/src/constants/project.ts +++ /dev/null @@ -1 +0,0 @@ -export const SUPPORT_EMAIL = 'support@kycnot.me' diff --git a/web/src/constants/serviceStatusChange.ts b/web/src/constants/serviceStatusChange.ts deleted file mode 100644 index 4ccc281..0000000 --- a/web/src/constants/serviceStatusChange.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { makeHelpersForOptions } from '../lib/makeHelpersForOptions' -import { transformCase } from '../lib/strings' - -import type { ServiceVerificationStatusChange } from '@prisma/client' - -type ServiceVerificationStatusChangeInfo = { - value: T - label: string - notificationTitle: string -} - -export const { - dataArray: serviceVerificationStatusChanges, - dataObject: serviceVerificationStatusChangesById, - getFn: getServiceVerificationStatusChangeInfo, - zodEnumById: serviceVerificationStatusChangesZodEnumById, -} = makeHelpersForOptions( - 'value', - (value): ServiceVerificationStatusChangeInfo => ({ - value, - label: value ? transformCase(value.replaceAll('_', ' '), 'title') : String(value), - notificationTitle: value ? transformCase(value.replaceAll('_', ' '), 'title') : String(value), - }), - [ - { - value: 'STATUS_CHANGED_TO_COMMUNITY_CONTRIBUTED', - label: 'status changed to community contributed', - notificationTitle: 'status changed to community contributed', - }, - { - value: 'STATUS_CHANGED_TO_APPROVED', - label: 'status changed to approved', - notificationTitle: 'status changed to approved', - }, - { - value: 'STATUS_CHANGED_TO_VERIFICATION_SUCCESS', - label: 'status changed to verification success', - notificationTitle: 'status changed to verification success', - }, - { - value: 'STATUS_CHANGED_TO_VERIFICATION_FAILED', - label: 'status changed to verification failed', - notificationTitle: 'status changed to verification failed', - }, - ] as const satisfies ServiceVerificationStatusChangeInfo[] -) - -export type ServiceVerificationStatusChangeType = (typeof serviceVerificationStatusChanges)[number]['value'] diff --git a/web/src/constants/serviceSuggestionStatus.ts b/web/src/constants/serviceSuggestionStatus.ts deleted file mode 100644 index 4c73adb..0000000 --- a/web/src/constants/serviceSuggestionStatus.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { makeHelpersForOptions } from '../lib/makeHelpersForOptions' -import { transformCase } from '../lib/strings' - -import type { ServiceSuggestionStatus } from '@prisma/client' - -type ServiceSuggestionStatusInfo = { - value: T - slug: string - label: string - icon: string - iconClass: string - default: boolean -} - -export const { - dataArray: serviceSuggestionStatuses, - dataObject: serviceSuggestionStatusesById, - getFn: getServiceSuggestionStatusInfo, - getFnSlug: getServiceSuggestionStatusInfoBySlug, - zodEnumBySlug: serviceSuggestionStatusesZodEnumBySlug, - zodEnumById: serviceSuggestionStatusesZodEnumById, - keyToSlug: serviceSuggestionStatusIdToSlug, - slugToKey: serviceSuggestionStatusSlugToId, -} = makeHelpersForOptions( - 'value', - (value): ServiceSuggestionStatusInfo => ({ - value, - slug: value ? value.toLowerCase() : '', - label: value ? transformCase(value, 'title') : String(value), - icon: 'ri:question-line', - iconClass: 'text-current/60', - default: false, - }), - [ - { - value: 'PENDING', - slug: 'pending', - label: 'Pending', - icon: 'ri:time-line', - iconClass: 'text-yellow-400', - default: true, - }, - { - value: 'APPROVED', - slug: 'approved', - label: 'Approved', - icon: 'ri:check-line', - iconClass: 'text-green-400', - default: false, - }, - { - value: 'REJECTED', - slug: 'rejected', - label: 'Rejected', - icon: 'ri:close-line', - iconClass: 'text-red-400', - default: false, - }, - { - value: 'WITHDRAWN', - slug: 'withdrawn', - label: 'Withdrawn', - icon: 'ri:arrow-left-line', - iconClass: 'text-gray-400', - default: false, - }, - ] as const satisfies ServiceSuggestionStatusInfo[] -) diff --git a/web/src/constants/serviceSuggestionType.ts b/web/src/constants/serviceSuggestionType.ts deleted file mode 100644 index 2efe804..0000000 --- a/web/src/constants/serviceSuggestionType.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { makeHelpersForOptions } from '../lib/makeHelpersForOptions' -import { transformCase } from '../lib/strings' - -import type { ServiceSuggestionType } from '@prisma/client' - -type ServiceSuggestionTypeInfo = { - value: T - slug: string - label: string - icon: string - default: boolean -} - -export const { - dataArray: serviceSuggestionTypes, - dataObject: serviceSuggestionTypesById, - getFn: getServiceSuggestionTypeInfo, - getFnSlug: getServiceSuggestionTypeInfoBySlug, - zodEnumBySlug: serviceSuggestionTypesZodEnumBySlug, - zodEnumById: serviceSuggestionTypesZodEnumById, - keyToSlug: serviceSuggestionTypeIdToSlug, - slugToKey: serviceSuggestionTypeSlugToId, -} = makeHelpersForOptions( - 'value', - (value): ServiceSuggestionTypeInfo => ({ - value, - slug: value ? value.toLowerCase() : '', - label: value ? transformCase(value, 'title') : String(value), - icon: 'ri:question-line', - default: false, - }), - [ - { - value: 'CREATE_SERVICE', - slug: 'create', - label: 'Create', - icon: 'ri:add-line', - default: true, - }, - { - value: 'EDIT_SERVICE', - slug: 'edit', - label: 'Edit', - icon: 'ri:pencil-line', - default: false, - }, - ] as const satisfies ServiceSuggestionTypeInfo[] -) diff --git a/web/src/constants/serviceUserRoles.ts b/web/src/constants/serviceUserRoles.ts deleted file mode 100644 index fbcd570..0000000 --- a/web/src/constants/serviceUserRoles.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { makeHelpersForOptions } from '../lib/makeHelpersForOptions' -import { transformCase } from '../lib/strings' - -import type BadgeSmall from '../components/BadgeSmall.astro' -import type { ServiceUserRole } from '@prisma/client' -import type { ComponentProps } from 'astro/types' - -type ServiceUserRoleInfo = { - value: T - slug: string - label: string - icon: string - order: number - color: NonNullable['color']> -} - -export const { - dataArray: serviceUserRoles, - dataObject: serviceUserRolesById, - getFn: getServiceUserRoleInfo, -} = makeHelpersForOptions( - 'value', - (value): ServiceUserRoleInfo => ({ - value, - slug: value ? value.toLowerCase() : '', - label: value ? transformCase(value, 'title').replace('_', ' ') : String(value), - icon: 'ri:user-3-line', - order: Infinity, - color: 'gray', - }), - [ - { - value: 'OWNER', - slug: 'owner', - label: 'Owner', - icon: 'ri:vip-crown-2-fill', - order: 1, - color: 'lime', - }, - { - value: 'ADMIN', - slug: 'admin', - label: 'Admin', - icon: 'ri:shield-star-fill', - order: 2, - color: 'green', - }, - { - value: 'MODERATOR', - slug: 'moderator', - label: 'Moderator', - icon: 'ri:glasses-2-line', - order: 3, - color: 'teal', - }, - { - value: 'SUPPORT', - slug: 'support', - label: 'Support', - icon: 'ri:customer-service-2-fill', - order: 4, - color: 'blue', - }, - { - value: 'TEAM_MEMBER', - slug: 'team_member', - label: 'Team Member', - icon: 'ri:team-fill', - order: 5, - color: 'cyan', - }, - ] as const satisfies ServiceUserRoleInfo[] -) diff --git a/web/src/constants/serviceVisibility.ts b/web/src/constants/serviceVisibility.ts deleted file mode 100644 index 2403658..0000000 --- a/web/src/constants/serviceVisibility.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { makeHelpersForOptions } from '../lib/makeHelpersForOptions' -import { transformCase } from '../lib/strings' - -import type { ServiceVisibility } from '@prisma/client' - -type ServiceVisibilityInfo = { - value: T - slug: string - label: string - description: string - icon: string - iconClass: string -} - -export const { - dataArray: serviceVisibilities, - dataObject: serviceVisibilitiesById, - getFn: getServiceVisibilityInfo, - getFnSlug: getServiceVisibilityInfoBySlug, - zodEnumBySlug: serviceVisibilitiesZodEnumBySlug, - zodEnumById: serviceVisibilitiesZodEnumById, - keyToSlug: serviceVisibilityIdToSlug, - slugToKey: serviceVisibilitySlugToId, -} = makeHelpersForOptions( - 'value', - (value): ServiceVisibilityInfo => ({ - value, - slug: value ? value.toLowerCase() : '', - label: value ? transformCase(value, 'title') : String(value), - description: '', - icon: 'ri:eye-line', - iconClass: 'text-current/60', - }), - [ - { - value: 'PUBLIC', - slug: 'public', - label: 'Public', - description: 'Listed in search and browse.', - icon: 'ri:global-line', - iconClass: 'text-green-500', - }, - { - value: 'UNLISTED', - slug: 'unlisted', - label: 'Unlisted', - description: 'Only accessible via direct link.', - icon: 'ri:link', - iconClass: 'text-yellow-500', - }, - { - value: 'HIDDEN', - slug: 'hidden', - label: 'Hidden', - description: 'Only visible to moderators.', - icon: 'ri:lock-line', - iconClass: 'text-red-500', - }, - ] as const satisfies ServiceVisibilityInfo[] -) diff --git a/web/src/constants/splashTexts.ts b/web/src/constants/splashTexts.ts deleted file mode 100644 index 775480d..0000000 --- a/web/src/constants/splashTexts.ts +++ /dev/null @@ -1,17 +0,0 @@ -export const splashTexts: string[] = [ - 'Privacy is not a crime.', - 'True financial independence.', - 'Privacy is a human right.', - 'Cypherpunk zone ahead.', - 'KYC? Not me!', - 'Freedom through privacy.', - 'Resist surveillance.', - 'Anonymity is power.', - 'Defend your privacy.', - 'Unbank yourself.', - 'Banking without borders.', - 'Escape the panopticon.', - 'Ditch the gatekeepers.', - 'Own your identity.', - 'Financial privacy matters.', -] diff --git a/web/src/constants/suggestionStatusChange.ts b/web/src/constants/suggestionStatusChange.ts deleted file mode 100644 index 9b5532b..0000000 --- a/web/src/constants/suggestionStatusChange.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { makeHelpersForOptions } from '../lib/makeHelpersForOptions' -import { transformCase } from '../lib/strings' - -import type { ServiceSuggestionStatusChange } from '@prisma/client' - -type ServiceSuggestionStatusChangeInfo = { - value: T - label: string - notificationTitle: string -} - -export const { - dataArray: serviceSuggestionStatusChanges, - dataObject: serviceSuggestionStatusChangesById, - getFn: getServiceSuggestionStatusChangeInfo, - zodEnumById: serviceSuggestionStatusChangesZodEnumById, -} = makeHelpersForOptions( - 'value', - (value): ServiceSuggestionStatusChangeInfo => ({ - value, - label: value ? transformCase(value.replaceAll('_', ' '), 'title') : String(value), - notificationTitle: value ? transformCase(value.replaceAll('_', ' '), 'title') : String(value), - }), - [ - { - value: 'STATUS_CHANGED_TO_PENDING', - label: 'status changed to pending', - notificationTitle: 'status changed to pending', - }, - { - value: 'STATUS_CHANGED_TO_APPROVED', - label: 'status changed to approved', - notificationTitle: 'status changed to approved', - }, - { - value: 'STATUS_CHANGED_TO_REJECTED', - label: 'status changed to rejected', - notificationTitle: 'status changed to rejected', - }, - { - value: 'STATUS_CHANGED_TO_WITHDRAWN', - label: 'status changed to withdrawn', - notificationTitle: 'status changed to withdrawn', - }, - ] as const satisfies ServiceSuggestionStatusChangeInfo[] -) - -export type ServiceSuggestionStatusChangeType = (typeof serviceSuggestionStatusChanges)[number]['value'] diff --git a/web/src/constants/tosHighlightRating.ts b/web/src/constants/tosHighlightRating.ts deleted file mode 100644 index a4faf0e..0000000 --- a/web/src/constants/tosHighlightRating.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { makeHelpersForOptions } from '../lib/makeHelpersForOptions' -import { transformCase } from '../lib/strings' - -type TosHighlightRatingInfo = { - id: T - icon: string - name: string - classNames: { - icon: string - borderColor: string - } - order: number -} - -export const { - dataArray: tosHighlightRatings, - dataObject: tosHighlightRatingsById, - getFn: getTosHighlightRatingInfo, -} = makeHelpersForOptions( - 'id', - (id): TosHighlightRatingInfo => ({ - id, - icon: 'ri:question-line', - name: id ? transformCase(id, 'title') : String(id), - classNames: { - icon: 'text-yellow-400', - borderColor: 'border-yellow-500/40', - }, - order: Infinity, - }), - [ - { - id: 'negative', - icon: 'ri:thumb-down-line', - name: 'Negative', - classNames: { - icon: 'text-red-400', - borderColor: 'border-red-500/40', - }, - order: 1, - }, - { - id: 'positive', - icon: 'ri:thumb-up-line', - name: 'Positive', - classNames: { - icon: 'text-green-400', - borderColor: 'border-green-500/40', - }, - order: 2, - }, - { - id: 'neutral', - icon: 'ri:information-line', - name: 'Neutral', - classNames: { - icon: 'text-blue-400', - borderColor: 'border-blue-500/40', - }, - order: 3, - }, - ] as const satisfies TosHighlightRatingInfo[] -) diff --git a/web/src/constants/userSentiment.ts b/web/src/constants/userSentiment.ts deleted file mode 100644 index 26103c0..0000000 --- a/web/src/constants/userSentiment.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { makeHelpersForOptions } from '../lib/makeHelpersForOptions' -import { transformCase } from '../lib/strings' - -type UserSentimentInfo = { - id: T - icon: string - name: string - classNames: { - icon: string - borderColor: string - background: string - } -} - -export const { - dataArray: userSentiments, - dataObject: userSentimentsById, - getFn: getUserSentimentInfo, -} = makeHelpersForOptions( - 'id', - (id): UserSentimentInfo => ({ - id, - icon: 'ri:emotion-normal-line', - name: id ? transformCase(id, 'title') : String(id), - classNames: { - icon: 'text-yellow-400', - borderColor: 'border-yellow-500/40', - background: 'bg-yellow-950/20', - }, - }), - [ - { - id: 'positive', - icon: 'ri:emotion-happy-line', - name: 'Positive', - classNames: { - icon: 'text-green-400', - borderColor: 'border-green-500/40', - background: 'bg-green-950/20', - }, - }, - { - id: 'neutral', - icon: 'ri:emotion-normal-line', - name: 'Neutral', - classNames: { - icon: 'text-blue-400', - borderColor: 'border-blue-500/40', - background: 'bg-blue-950/20', - }, - }, - { - id: 'negative', - icon: 'ri:emotion-unhappy-line', - name: 'Negative', - classNames: { - icon: 'text-red-400', - borderColor: 'border-red-500/40', - background: 'bg-red-950/20', - }, - }, - ] as const satisfies UserSentimentInfo[] -) diff --git a/web/src/constants/verificationStatus.ts b/web/src/constants/verificationStatus.ts deleted file mode 100644 index 4c0c73d..0000000 --- a/web/src/constants/verificationStatus.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { makeHelpersForOptions } from '../lib/makeHelpersForOptions' -import { transformCase } from '../lib/strings' - -import type { MarkdownString } from '../lib/markdown' -import type { VerificationStatus } from '@prisma/client' - -type VerificationStatusInfo = { - value: T - slug: string - labelShort: string - label: string - icon: string - default: boolean - description: string - privacyPoints: number - trustPoints: number - classNames: { - icon: string - badgeBig: string - button: string - description: string - containerBg: string - } - order: number - verbPast: string -} - -export const READ_MORE_SENTENCE_LINK = - 'Read more about the [suggestion review process](/about#suggestion-review-process).' satisfies MarkdownString - -export const { - dataArray: verificationStatuses, - dataObject: verificationStatusesByValue, - getFn: getVerificationStatusInfo, - getFnSlug: getVerificationStatusInfoBySlug, - zodEnumBySlug: verificationStatusesZodEnumBySlug, - zodEnumById: verificationStatusesZodEnumById, - keyToSlug: verificationStatusIdToSlug, - slugToKey: verificationStatusSlugToId, -} = makeHelpersForOptions( - 'value', - (value): VerificationStatusInfo => ({ - value, - slug: value ? value.toLowerCase() : '', - labelShort: value ? transformCase(value, 'title') : String(value), - label: value ? transformCase(value, 'title') : String(value), - icon: 'ri:loader-line', - default: false, - description: '', - privacyPoints: 0, - trustPoints: 0, - classNames: { - icon: 'text-current', - badgeBig: 'bg-night-400 text-day-100', - button: 'bg-night-400 hover:bg-night-300', - description: 'text-day-200', - containerBg: 'bg-night-600', - }, - order: Infinity, - verbPast: value ? transformCase(value, 'title') : String(value), - }), - [ - { - value: 'VERIFICATION_SUCCESS', - slug: 'verified', - labelShort: 'Verified', - label: 'Verified', - icon: 'ri:verified-badge-fill', - default: true, - description: - 'Thoroughly tested and verified by the team. But things might change, this is not a guarantee.', - privacyPoints: 0, - trustPoints: 5, - classNames: { - icon: 'text-[#40e6c2]', - badgeBig: 'bg-green-800/50 text-green-100', - button: 'bg-green-700 hover:bg-green-600', - description: 'text-green-200', - containerBg: 'bg-green-900/30', - }, - order: 1, - verbPast: 'verified', - }, - { - value: 'APPROVED', - slug: 'approved', - labelShort: 'Approved', - label: 'Approved', - icon: 'ri:check-line', - default: true, - description: - 'Everything checks out at first glance, but not verified nor thoroughly tested by the team.', - privacyPoints: 0, - trustPoints: 5, - classNames: { - icon: 'text-white', - badgeBig: 'bg-night-400 text-day-100', - button: 'bg-night-400 hover:bg-night-300', - description: 'text-day-200', - containerBg: 'bg-night-600', - }, - order: 2, - verbPast: 'approved', - }, - { - value: 'COMMUNITY_CONTRIBUTED', - slug: 'community', - labelShort: 'Community', - label: 'Community Contributed', - icon: 'ri:question-line', - default: false, - description: 'Suggested by the community, but not reviewed by the team yet.', - privacyPoints: 0, - trustPoints: 0, - classNames: { - icon: 'text-yellow-400', - badgeBig: 'bg-amber-800/50 text-amber-100', - button: 'bg-amber-700 hover:bg-amber-600', - description: 'text-amber-200', - containerBg: 'bg-amber-900/30', - }, - order: 3, - verbPast: 'contributed by the community', - }, - { - value: 'VERIFICATION_FAILED', - slug: 'scam', - labelShort: 'Scam', - label: 'Scam', - icon: 'ri:alert-fill', - default: false, - description: 'Confirmed as a SCAM or not what it claims to be.', - privacyPoints: 0, - trustPoints: -30, - classNames: { - icon: 'text-red-500', - badgeBig: 'bg-red-800/50 text-red-100', - button: 'bg-red-700 hover:bg-red-600', - description: 'text-red-200', - containerBg: 'bg-red-900/30', - }, - order: 4, - verbPast: 'marked as a SCAM', - }, - ] as const satisfies VerificationStatusInfo[] -) diff --git a/web/src/env.d.ts b/web/src/env.d.ts deleted file mode 100644 index 6018113..0000000 --- a/web/src/env.d.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { ErrorBanners } from './lib/errorBanners' -import type { KarmaUnlocks } from './lib/karmaUnlocks' -/* eslint-disable @typescript-eslint/consistent-type-definitions */ -import type { Prisma } from '@prisma/client' -import type * as htmx from 'htmx.org' - -declare global { - namespace App { - interface Locals { - user: (Prisma.UserGetPayload & { karmaUnlocks: KarmaUnlocks }) | null - actualUser: (Prisma.UserGetPayload & { karmaUnlocks: KarmaUnlocks }) | null - banners: ErrorBanners - makeId: (prefix: T) => `${T}-${number}-${string}` - } - } - - interface Window { - htmx?: typeof htmx - } - - namespace PrismaJson { - type TosReview = { - kycLevel: 0 | 1 | 2 | 3 | 4 - /** Less than 200 characters */ - summary: MarkdownString - contentHash: string - complexity: 'high' | 'low' | 'medium' - highlights: { - /** Very short */ - title: string - /** Short */ - content: MarkdownString - rating: 'negative' | 'neutral' | 'positive' - }[] - } - - type UserSentiment = { - summary: MarkdownString - sentiment: 'negative' | 'neutral' | 'positive' - whatUsersLike: string[] - whatUsersDislike: string[] - } - } -} diff --git a/web/src/icons/anonymous-mask.svg b/web/src/icons/anonymous-mask.svg deleted file mode 100644 index 9d058a1..0000000 --- a/web/src/icons/anonymous-mask.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - \ No newline at end of file diff --git a/web/src/icons/bitcoin.svg b/web/src/icons/bitcoin.svg deleted file mode 100644 index c86bd39..0000000 --- a/web/src/icons/bitcoin.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - \ No newline at end of file diff --git a/web/src/icons/coins.svg b/web/src/icons/coins.svg deleted file mode 100644 index 62d2daa..0000000 --- a/web/src/icons/coins.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/web/src/icons/credit-card.svg b/web/src/icons/credit-card.svg deleted file mode 100644 index 03d2c95..0000000 --- a/web/src/icons/credit-card.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/web/src/icons/diamond-question.svg b/web/src/icons/diamond-question.svg deleted file mode 100644 index d31d430..0000000 --- a/web/src/icons/diamond-question.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/web/src/icons/fingerprint-detailed.svg b/web/src/icons/fingerprint-detailed.svg deleted file mode 100644 index 535a827..0000000 --- a/web/src/icons/fingerprint-detailed.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - \ No newline at end of file diff --git a/web/src/icons/gun.svg b/web/src/icons/gun.svg deleted file mode 100644 index 327a2e9..0000000 --- a/web/src/icons/gun.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/web/src/icons/handcuffs.svg b/web/src/icons/handcuffs.svg deleted file mode 100644 index 5e83980..0000000 --- a/web/src/icons/handcuffs.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - \ No newline at end of file diff --git a/web/src/icons/i2p.svg b/web/src/icons/i2p.svg deleted file mode 100644 index 8392463..0000000 --- a/web/src/icons/i2p.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - \ No newline at end of file diff --git a/web/src/icons/monero.svg b/web/src/icons/monero.svg deleted file mode 100644 index 23e341b..0000000 --- a/web/src/icons/monero.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/web/src/icons/onion.svg b/web/src/icons/onion.svg deleted file mode 100644 index 4c839a9..0000000 --- a/web/src/icons/onion.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - \ No newline at end of file diff --git a/web/src/layouts/BaseLayout.astro b/web/src/layouts/BaseLayout.astro deleted file mode 100644 index 6206597..0000000 --- a/web/src/layouts/BaseLayout.astro +++ /dev/null @@ -1,101 +0,0 @@ ---- -import BaseHead from '../components/BaseHead.astro' -import Footer from '../components/Footer.astro' -import Header from '../components/Header.astro' -import { cn } from '../lib/cn' - -import type { AstroChildren } from '../lib/astro' -import type { ComponentProps } from 'astro/types' - -import '@fontsource-variable/space-grotesk' -import '../styles/global.css' - -type Props = ComponentProps & { - children: AstroChildren - errors?: string[] - success?: string[] - className?: { - body?: string - main?: string - footer?: string - } - showSplashText?: boolean - widthClassName?: - | 'container' - | 'max-w-none' - | 'max-w-screen-2xl' - | 'max-w-screen-lg' - | 'max-w-screen-md' - | 'max-w-screen-sm' - | 'max-w-screen-xl' - | 'max-w-screen-xs' -} - -const { - errors = [], - success = [], - className, - widthClassName = 'max-w-screen-2xl', - showSplashText, - ...baseHeadProps -} = Astro.props - -const actualErrors = [...errors, ...Astro.locals.banners.errors] -const actualSuccess = [...success, ...Astro.locals.banners.successes] ---- - - - - - - - - -
- - { - actualSuccess.length > 0 && ( -
    - {actualSuccess.map((message) => ( -
  • - {message} -
  • - ))} -
- ) - } - - { - actualErrors.length > 0 && ( -
    - {actualErrors.map((error) => ( -
  • - {error} -
  • - ))} -
- ) - } - -
- -
- -
- - diff --git a/web/src/layouts/MarkdownLayout.astro b/web/src/layouts/MarkdownLayout.astro deleted file mode 100644 index 54b69c8..0000000 --- a/web/src/layouts/MarkdownLayout.astro +++ /dev/null @@ -1,72 +0,0 @@ ---- -import { makeOgImageUrl, type OgImageAllTemplatesWithProps } from '../components/OgImage' -import { KYCNOTME_SCHEMA_MINI } from '../lib/schema' - -import BaseLayout from './BaseLayout.astro' - -import type { AstroChildren } from '../lib/astro' -import type { MarkdownLayoutProps } from 'astro' -import type { ComponentProps } from 'astro/types' -import type { Article, WithContext } from 'schema-dts' - -type Props = ComponentProps & - MarkdownLayoutProps<{ - children: AstroChildren - title: string - author: string - pubDate: string - description: string - }> - -const { frontmatter, schemas, ...baseLayoutProps } = Astro.props -const publishDate = frontmatter.pubDate ? new Date(frontmatter.pubDate) : null -const ogImageTemplateData = { - template: 'generic', - title: frontmatter.title, -} satisfies OgImageAllTemplatesWithProps -const weAreAuthor = frontmatter.author.toLowerCase().trim() === 'kycnot.me' ---- - -, - ...(schemas ?? []), - ]} -> -
-

{frontmatter.title}

-

- {frontmatter.author && `by ${frontmatter.author}`} - {frontmatter.pubDate && ` | ${new Date(frontmatter.pubDate).toLocaleDateString()}`} -

- - -
-
diff --git a/web/src/layouts/MiniLayout.astro b/web/src/layouts/MiniLayout.astro deleted file mode 100644 index 5cf11b0..0000000 --- a/web/src/layouts/MiniLayout.astro +++ /dev/null @@ -1,38 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' - -import { cn } from '../lib/cn' - -import BaseLayout from './BaseLayout.astro' - -import type { ComponentProps } from 'astro/types' - -type Props = Omit, 'widthClassName'> & { - layoutHeader: { icon: string; title: string; subtitle: string } -} - -const { layoutHeader, ...baseLayoutProps } = Astro.props ---- - - -
-
- -
- -

- {layoutHeader.title} -

-

{layoutHeader.subtitle}

- - -
-
diff --git a/web/src/lib/accountCreate.ts b/web/src/lib/accountCreate.ts deleted file mode 100644 index ea12faa..0000000 --- a/web/src/lib/accountCreate.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { generateUsername } from 'unique-username-generator' - -import { prisma } from './prisma' -import { generateUserSecretToken, hashUserSecretToken } from './userSecretToken' - -/** - * Generate a random username. - * Format: `adjective_noun_1234` - */ -function generateRandomUsername() { - return `${generateUsername('_')}_${Math.floor(Math.random() * 10000).toString()}` -} - -export async function createAccount(preGeneratedToken?: string) { - const token = preGeneratedToken ?? generateUserSecretToken() - const hash = hashUserSecretToken(token) - const username = generateRandomUsername() - - const user = await prisma.user.create({ - data: { - name: username, - secretTokenHash: hash, - notificationPreferences: { create: {} }, - }, - }) - - return { token, user } -} diff --git a/web/src/lib/arrays.ts b/web/src/lib/arrays.ts deleted file mode 100644 index 4cfb673..0000000 --- a/web/src/lib/arrays.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { z } from 'astro/zod' -import { map, xor, some, every } from 'lodash-es' - -/** - * Returns a tuple of values from a tuple of records. - * - * @example - * TupleValues<'value', [{ value: 'a' }, { value: 'b' }]> // -> ['a', 'b'] - */ -type TupleValues[]> = { - [I in keyof T]: T[I][K] -} - -/** - * Returns a tuple of values from a tuple of records. - * - * @example - * const options = [{ value: 'a' }, { value: 'b' }] - * const values = getTupleValues(options, 'value') // -> ['a', 'b'] - */ -export function getTupleValues[]>( - arr: T, - key: K -): TupleValues { - return map(arr, key) as unknown as TupleValues -} - -/** - * Returns a zod enum from a constant. - * - * @example - * const options = [{ value: 'a' }, { value: 'b' }] - * const zodEnum = zodEnumFromConstant(options, 'value') // -> z.enum(['a', 'b']) - */ -export function zodEnumFromConstant[]>( - arr: T, - key: K -) { - return z.enum(getTupleValues(arr as unknown as [T[number], ...T[number][]], key)) -} - -/** - * Returns a random element from an array. - * - * @example - * const options = ['a', 'b'] - * const randomElement = getRandomElement(options) // -> 'a' or 'b' - */ -export function getRandom(array: readonly T[]): T { - return array[Math.floor(Math.random() * array.length)] as T -} - -/** - * Typed version of Array.prototype.join. - * - * @example - * TypedJoin<['a', 'b']> // -> 'ab' - * TypedJoin<['a', 'b'], '-'> // -> 'a-b' - */ -type TypedJoin< - T extends readonly string[], - Separator extends string = '', - First extends boolean = true, -> = T extends readonly [] - ? '' - : T extends readonly [infer U extends string, ...infer R extends readonly string[]] - ? `${First extends true ? '' : Separator}${U}${TypedJoin}` - : string - -/** - * Joins an array of strings with a separator. - * - * @example - * const options = ['a', 'b'] as const - * const joined = typedJoin(options) // -> 'ab' - * const joinedWithSeparator = typedJoin(options, '-') // -> 'a-b' - */ -export function typedJoin( - array: T, - separator: Separator = '' as Separator -) { - return array.join(separator) as TypedJoin -} - -/** - * Checks if two or more arrays are equal without considering order. - * - * @example - * const a = [1, 2, 3] - * const b = [3, 2, 1] - * areEqualArraysWithoutOrder(a, b) // -> true - */ -export function areEqualArraysWithoutOrder(...arrays: (T[] | null | undefined)[]): boolean { - return xor(...arrays).length === 0 -} - -/** - * Checks if some but not all of the arguments are true. - * - * @example - * someButNotAll(true, false, true) // -> true - * someButNotAll(true, true, true) // -> false - * someButNotAll(false, false, false) // -> false - */ -export const someButNotAll = (...args: boolean[]) => some(args) && !every(args) - -/** - * Returns undefined if the array is empty. - * - * @example - * const a = [1, 2, 3] - * const b = [] - * const c = null - * const d = undefined - * undefinedIfEmpty(a) // -> [1, 2, 3] - * undefinedIfEmpty(b) // -> undefined - * undefinedIfEmpty(c) // -> undefined - * undefinedIfEmpty(d) // -> undefined - */ -export function undefinedIfEmpty(value: T) { - return (value && Array.isArray(value) && value.length > 0 ? value : undefined) as T extends (infer U)[] - ? U[] | undefined - : T extends readonly [...infer U] - ? U | undefined - : undefined -} - -export function isNotArray(value: T | T[]): value is T { - return !Array.isArray(value) -} diff --git a/web/src/lib/astro.ts b/web/src/lib/astro.ts deleted file mode 100644 index fb165d1..0000000 --- a/web/src/lib/astro.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-empty-object-type */ -import type { ComponentProps, HTMLTag, Polymorphic } from 'astro/types' - -export type AstroComponent = (args: any) => any - -export type PolymorphicComponent = - (Component extends AstroComponent ? ComponentProps & { as?: Component } : {}) & - (Component extends HTMLTag ? Polymorphic<{ as: Component }> : {}) - -export type AstroChildren = any diff --git a/web/src/lib/astroActions.ts b/web/src/lib/astroActions.ts deleted file mode 100644 index 2eb6849..0000000 --- a/web/src/lib/astroActions.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import type { z } from 'astro/zod' -import type { ActionAccept, ActionClient, SafeResult } from 'astro:actions' - -export type AnyAction = ActionClient & string - -export type FormAction = ActionClient & string -export type JsonAction = ActionClient & string - -export type ActionInput = Parameters[0] - -export type ActionOutput = - ReturnType extends Promise> ? TOutput : never - -/** Returns the input type of an action, or the output type if the input type is not a record. */ -export type ActionInputNoFormData = - ActionInput extends Record ? ActionInput : ActionOutput diff --git a/web/src/lib/attributes.ts b/web/src/lib/attributes.ts deleted file mode 100644 index b85ceaf..0000000 --- a/web/src/lib/attributes.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { orderBy } from 'lodash-es' - -import { getAttributeCategoryInfo } from '../constants/attributeCategories' -import { getAttributeTypeInfo } from '../constants/attributeTypes' -import { READ_MORE_SENTENCE_LINK, verificationStatusesByValue } from '../constants/verificationStatus' - -import { formatDateShort } from './timeAgo' - -import type { Prisma } from '@prisma/client' - -export function sortAttributes< - T extends Prisma.AttributeGetPayload<{ - select: { - title: true - privacyPoints: true - trustPoints: true - category: true - type: true - } - }>, ->(attributes: T[]): T[] { - return orderBy( - attributes, - [ - ({ privacyPoints, trustPoints }) => (privacyPoints + trustPoints < 0 ? 1 : 2), - ({ privacyPoints, trustPoints }) => Math.abs(privacyPoints + trustPoints), - ({ type }) => getAttributeTypeInfo(type).order, - ({ category }) => getAttributeCategoryInfo(category).order, - 'title', - ], - ['asc', 'desc', 'asc', 'asc', 'asc'] - ) -} - -export function makeNonDbAttributes( - service: Prisma.ServiceGetPayload<{ - select: { - verificationStatus: true - isRecentlyListed: true - listedAt: true - createdAt: true - } - }>, - { filter = false }: { filter?: boolean } = {} -) { - const nonDbAttributes: (Prisma.AttributeGetPayload<{ - select: { - title: true - type: true - category: true - description: true - privacyPoints: true - trustPoints: true - } - }> & { - show: boolean - links: { - url: string - label: string - icon: string - }[] - })[] = [ - { - title: 'Verified', - show: service.verificationStatus === 'VERIFICATION_SUCCESS', - type: 'GOOD', - category: 'TRUST', - description: `${verificationStatusesByValue.VERIFICATION_SUCCESS.description} ${READ_MORE_SENTENCE_LINK}\n\nCheck out the [proof](#verification).`, - privacyPoints: verificationStatusesByValue.VERIFICATION_SUCCESS.privacyPoints, - trustPoints: verificationStatusesByValue.VERIFICATION_SUCCESS.trustPoints, - links: [ - { - url: '/?verification=verified', - label: 'Search with this', - icon: 'ri:search-line', - }, - ], - }, - { - title: 'Approved', - show: service.verificationStatus === 'APPROVED', - type: 'INFO', - category: 'TRUST', - description: `${verificationStatusesByValue.APPROVED.description} ${READ_MORE_SENTENCE_LINK}`, - privacyPoints: verificationStatusesByValue.APPROVED.privacyPoints, - trustPoints: verificationStatusesByValue.APPROVED.trustPoints, - links: [ - { - url: '/?verification=verified&verification=approved', - label: 'Search with this', - icon: 'ri:search-line', - }, - ], - }, - { - title: 'Community contributed', - show: service.verificationStatus === 'COMMUNITY_CONTRIBUTED', - type: 'WARNING', - category: 'TRUST', - description: `${verificationStatusesByValue.COMMUNITY_CONTRIBUTED.description} ${READ_MORE_SENTENCE_LINK}`, - privacyPoints: verificationStatusesByValue.COMMUNITY_CONTRIBUTED.privacyPoints, - trustPoints: verificationStatusesByValue.COMMUNITY_CONTRIBUTED.trustPoints, - links: [ - { - url: '/?verification=community', - label: 'With this', - icon: 'ri:search-line', - }, - { - url: '/?verification=verified&verification=approved', - label: 'Without this', - icon: 'ri:search-line', - }, - ], - }, - { - title: 'Is SCAM', - show: service.verificationStatus === 'VERIFICATION_FAILED', - type: 'BAD', - category: 'TRUST', - description: `${verificationStatusesByValue.VERIFICATION_FAILED.description} ${READ_MORE_SENTENCE_LINK}\n\nCheck out the [proof](#verification).`, - privacyPoints: verificationStatusesByValue.VERIFICATION_FAILED.privacyPoints, - trustPoints: verificationStatusesByValue.VERIFICATION_FAILED.trustPoints, - links: [ - { - url: '/?verification=scam', - label: 'With this', - icon: 'ri:search-line', - }, - { - url: '/?verification=verified&verification=approved', - label: 'Without this', - icon: 'ri:search-line', - }, - ], - }, - { - title: 'Recently listed', - show: service.isRecentlyListed, - type: 'WARNING', - category: 'TRUST', - description: `Listed on KYCnot.me ${formatDateShort(service.listedAt ?? service.createdAt)}. Proceed with caution.`, - privacyPoints: 0, - trustPoints: -5, - links: [], - }, - ] - - if (filter) return nonDbAttributes.filter(({ show }) => show) - - return nonDbAttributes -} diff --git a/web/src/lib/callActionWithUrlParams.ts b/web/src/lib/callActionWithUrlParams.ts deleted file mode 100644 index 43c7a60..0000000 --- a/web/src/lib/callActionWithUrlParams.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { serialize } from 'object-to-formdata' - -import { urlParamsToFormData, urlParamsToObject } from './urls' - -import type { AstroGlobal } from 'astro' -import type { ActionAccept, ActionClient, SafeResult } from 'astro:actions' -import type { z } from 'astro:schema' - -/** - * Call an Action directly from an Astro page or API endpoint. - * - * The action input is obtained from the URL search params. - * - * Returns a Promise with the action result. - * - * It uses {@link AstroGlobal.callAction} internally. - * - * Example usage: - * - * ```typescript - * import { actions } from 'astro:actions'; - * - * const result = await callActionWithUrlParamsUnhandledErrors(Astro, actions.getPost, 'form'); - * ``` - */ -export function callActionWithUrlParamsUnhandledErrors< - TAccept extends ActionAccept, - TInputSchema extends z.ZodType, - TOutput, - TAction extends - | ActionClient - | ActionClient['orThrow'], - P extends Parameters[0], ->(context: AstroGlobal, action: TAction, accept: P extends FormData ? 'form' : 'json') { - const input = - accept === 'form' - ? urlParamsToFormData(context.url.searchParams) - : urlParamsToObject(context.url.searchParams) - - return context.callAction(action, input as P) -} - -/** - * Call an Action directly from an Astro page or API endpoint. - * - * The action input is obtained from the URL search params. - * - * Returns a Promise with the action result's data. - * - * It stores the errors in {@link context.locals.banners} - * - * It uses {@link AstroGlobal.callAction} internally. - * - * Example usage: - * - * ```typescript - * import { actions } from 'astro:actions'; - * - * const data = await callActionWithUrlParams(Astro, actions.getPost, 'form'); - * ``` - */ -export async function callActionWithUrlParams< - TAccept extends ActionAccept, - TInputSchema extends z.ZodType, - TOutput, - TAction extends - | ActionClient - | ActionClient['orThrow'], - P extends Parameters[0], - // eslint-disable-next-line @typescript-eslint/no-explicit-any - TInputSchema2 extends ReturnType extends Promise> ? I : never, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - TOutput2 extends ReturnType extends Promise> ? O : never, ->(context: AstroGlobal, action: TAction, accept: P extends FormData ? 'form' : 'json') { - const input = - accept === 'form' - ? urlParamsToFormData(context.url.searchParams) - : urlParamsToObject(context.url.searchParams) - - const result = (await context.callAction(action, input as P)) as SafeResult< - TInputSchema2, - Awaited - > - if (result.error) { - context.locals.banners.add({ - uiMessage: result.error.message, - type: 'error', - origin: 'action', - error: result.error, - }) - } - return result.data -} - -/** - * Call an Action directly from an Astro page or API endpoint. - * - * Returns a Promise with the action result. - * - * It uses {@link AstroGlobal.callAction} internally. - * - * Example usage: - * - * ```typescript - * import { actions } from 'astro:actions'; - * - * const input = { id: 123 } - * const result = await callActionWithObjectUnhandledErrors(Astro, actions.getPost, input, 'form'); - * ``` - */ -export function callActionWithObjectUnhandledErrors< - TAccept extends ActionAccept, - TInputSchema extends z.ZodType, - TOutput, - TAction extends - | ActionClient - | ActionClient['orThrow'], - P extends Parameters[0], - TInputSchema2 extends ReturnType extends Promise> ? I : never, ->( - context: AstroGlobal, - action: TAction, - input: [TInputSchema2] extends [FormData] | [never] ? undefined : TInputSchema2, - accept: P extends FormData ? 'form' : 'json' -) { - const parsedInput = accept === 'form' ? serialize(input) : input - - return context.callAction(action, parsedInput as P) -} - -/** - * Call an Action directly from an Astro page or API endpoint. - * - * The action input is a plain object. - * - * Returns a Promise with the action result's data. - * - * It stores the errors in {@link context.locals.banners} - * - * It uses {@link AstroGlobal.callAction} internally. - * - * Example usage: - * - * ```typescript - * import { actions } from 'astro:actions'; - * - * const input = { id: 123 } - * const data = await callActionWithObject(Astro, actions.getPost, input, 'form'); - * ``` - */ -export async function callActionWithObject< - TAccept extends ActionAccept, - TInputSchema extends z.ZodType, - TOutput, - TAction extends - | ActionClient - | ActionClient['orThrow'], - P extends Parameters[0], - // eslint-disable-next-line @typescript-eslint/no-explicit-any - TInputSchema2 extends ReturnType extends Promise> ? I : never, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - TOutput2 extends ReturnType extends Promise> ? O : never, ->( - context: AstroGlobal, - action: TAction, - input: [TInputSchema2] extends [FormData] | [never] ? undefined : TInputSchema2, - accept: P extends FormData ? 'form' : 'json' -) { - const parsedInput = accept === 'form' ? serialize(input) : input - - const result = (await context.callAction(action, parsedInput as P)) as SafeResult< - TInputSchema2, - Awaited - > - if (result.error) { - context.locals.banners.add({ - uiMessage: result.error.message, - type: 'error', - origin: 'action', - error: result.error, - }) - } - return result.data -} diff --git a/web/src/lib/captcha.ts b/web/src/lib/captcha.ts deleted file mode 100644 index 9b4a12b..0000000 --- a/web/src/lib/captcha.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { createHash } from 'crypto' - -import { createCanvas } from 'canvas' - -export const CAPTCHA_LENGTH = 6 -const CAPTCHA_CHARS = 'ABCDEFGHIJKMNOPRSTUVWXYZ123456789' // Notice that ambiguous characters are removed - -/** Hash a captcha value */ -function hashCaptchaValue(value: string): string { - return createHash('sha256').update(value).digest('hex') -} - -/** Generate a captcha image as a data URI */ -export function generateCaptchaImage(text: string) { - const width = 144 - const height = 48 - const canvas = createCanvas(width, height) - const ctx = canvas.getContext('2d') - - // Fill background with gradient - const gradient = ctx.createLinearGradient(0, 0, width, height) - gradient.addColorStop(0, '#1a202c') - gradient.addColorStop(1, '#2d3748') - ctx.fillStyle = gradient - ctx.fillRect(0, 0, width, height) - - // Add grid pattern background - ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)' - ctx.lineWidth = 1 - for (let i = 0; i < width; i += 10) { - ctx.beginPath() - ctx.moveTo(i, 0) - ctx.lineTo(i, height) - ctx.stroke() - } - for (let i = 0; i < height; i += 10) { - ctx.beginPath() - ctx.moveTo(0, i) - ctx.lineTo(width, i) - ctx.stroke() - } - - // Add wavy lines - for (let i = 0; i < 3; i++) { - const r = Math.floor(Math.random() * 200 + 55).toString() - const g = Math.floor(Math.random() * 200 + 55).toString() - const b = Math.floor(Math.random() * 200 + 55).toString() - ctx.strokeStyle = 'rgba(' + r + ', ' + g + ', ' + b + ', 0.5)' - ctx.lineWidth = Math.random() * 3 + 1 - - ctx.beginPath() - const startY = Math.random() * height - let curveX = 0 - let curveY = startY - - ctx.moveTo(curveX, curveY) - - while (curveX < width) { - const nextX = curveX + Math.random() * 20 + 10 - const nextY = startY + Math.sin(curveX / 20) * (Math.random() * 15 + 5) - ctx.quadraticCurveTo(curveX + (nextX - curveX) / 2, curveY + Math.random() * 20 - 10, nextX, nextY) - curveX = nextX - curveY = nextY - } - ctx.stroke() - } - - // Add random dots - for (let i = 0; i < 100; i++) { - const r = Math.floor(Math.random() * 200 + 55).toString() - const g = Math.floor(Math.random() * 200 + 55).toString() - const b = Math.floor(Math.random() * 200 + 55).toString() - const alpha = (Math.random() * 0.5 + 0.1).toString() - ctx.fillStyle = 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')' - - ctx.beginPath() - ctx.arc(Math.random() * width, Math.random() * height, Math.random() * 2 + 1, 0, Math.PI * 2) - ctx.fill() - } - - // Draw captcha text with shadow and more distortion - ctx.shadowColor = 'rgba(0, 0, 0, 0.5)' - ctx.shadowBlur = 3 - ctx.shadowOffsetX = 2 - ctx.shadowOffsetY = 2 - - // Calculate text positioning - const fontSize = Math.floor(height * 0.55) - const padding = 15 - const charWidth = (width - padding * 2) / text.length - - // Draw each character with various effects - for (let i = 0; i < text.length; i++) { - const r = Math.floor(Math.random() * 100 + 155).toString() - const g = Math.floor(Math.random() * 100 + 155).toString() - const b = Math.floor(Math.random() * 100 + 155).toString() - ctx.fillStyle = 'rgb(' + r + ', ' + g + ', ' + b + ')' - - // Vary font style for each character - const fontStyle = Math.random() > 0.5 ? 'bold' : 'normal' - const fontFamily = Math.random() > 0.5 ? 'monospace' : 'Arial' - const fontSizeStr = fontSize.toString() - ctx.font = fontStyle + ' ' + fontSizeStr + 'px ' + fontFamily - - // Position with variations - const xPos = padding + i * charWidth + (Math.random() * 8 - 4) - const yPos = height * 0.6 + (Math.random() * 12 - 6) - const rotation = Math.random() * 0.6 - 0.3 - const scale = 0.8 + Math.random() * 0.4 - - ctx.save() - ctx.translate(xPos, yPos) - ctx.rotate(rotation) - ctx.scale(scale, scale) - - // Add slight perspective effect - if (Math.random() > 0.7) { - ctx.transform(1, 0, 0.1, 1, 0, 0) - } else if (Math.random() > 0.5) { - ctx.transform(1, 0, -0.1, 1, 0, 0) - } - - ctx.fillText(text.charAt(i), 0, 0) - ctx.restore() - } - - // Add some noise on top - for (let x = 0; x < width; x += 3) { - for (let y = 0; y < height; y += 3) { - if (Math.random() > 0.95) { - const alpha = (Math.random() * 0.2 + 0.1).toString() - ctx.fillStyle = 'rgba(255, 255, 255, ' + alpha + ')' - ctx.fillRect(x, y, 2, 2) - } - } - } - - return { - src: canvas.toDataURL('image/png'), - width, - height, - format: 'png', - } as const satisfies ImageMetadata -} - -/** Generate a new captcha with solution, its hash, and image data URI */ -export function generateCaptcha() { - const solution = Array.from({ length: CAPTCHA_LENGTH }, () => - CAPTCHA_CHARS.charAt(Math.floor(Math.random() * CAPTCHA_CHARS.length)) - ).join('') - - return { - solution, - solutionHash: hashCaptchaValue(solution), - image: generateCaptchaImage(solution), - } -} - -/** Verify a captcha input against the expected hash */ -export function verifyCaptcha(value: string, solutionHash: string): boolean { - const correctedValue = value - .toUpperCase() - .replace(/[^A-Z0-9]/g, '') - .replace(/0/g, 'O') - .replace(/Q/g, 'O') - .replace(/L/g, 'I') - const valueHash = hashCaptchaValue(correctedValue) - return valueHash === solutionHash -} diff --git a/web/src/lib/captchaValidation.ts b/web/src/lib/captchaValidation.ts deleted file mode 100644 index 01094b9..0000000 --- a/web/src/lib/captchaValidation.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { z, type RefinementCtx } from 'astro/zod' - -import { CAPTCHA_LENGTH, verifyCaptcha } from './captcha' - -export const captchaFormSchemaProperties = { - 'captcha-value': z - .string() - .length(CAPTCHA_LENGTH, `Captcha must be ${CAPTCHA_LENGTH.toLocaleString()} characters long`) - .regex(/^[A-Za-z0-9]+$/, 'Captcha must contain only uppercase letters and numbers'), - 'captcha-solution-hash': z.string().min(1, 'Missing internal captcha data'), -} as const - -export const captchaFormSchemaSuperRefine = ( - value: z.infer>, - ctx: RefinementCtx -) => { - const isValidCaptcha = verifyCaptcha(value['captcha-value'], value['captcha-solution-hash']) - if (!isValidCaptcha) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - path: ['captcha-value'], - message: 'Incorrect captcha', - }) - } -} - -export const captchaFormSchema = z - .object(captchaFormSchemaProperties) - .superRefine(captchaFormSchemaSuperRefine) diff --git a/web/src/lib/cn.ts b/web/src/lib/cn.ts deleted file mode 100644 index 93654ae..0000000 --- a/web/src/lib/cn.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { clsx, type ClassValue } from 'clsx' -import { extendTailwindMerge } from 'tailwind-merge' - -const twMerge = extendTailwindMerge({ - extend: { - classGroups: { - 'font-family': ['font-title'], - }, - }, -}) - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) -} diff --git a/web/src/lib/commentsWithReplies.ts b/web/src/lib/commentsWithReplies.ts deleted file mode 100644 index 186ca0f..0000000 --- a/web/src/lib/commentsWithReplies.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { z } from 'zod' - -import type { Prisma } from '@prisma/client' - -export const MAX_COMMENT_DEPTH = 12 - -const commentReplyQuery = { - select: { - id: true, - status: true, - suspicious: true, - upvotes: true, - requiresAdminReview: true, - createdAt: true, - communityNote: true, - internalNote: true, - privateContext: true, - content: true, - serviceId: true, - parentId: true, - rating: true, - ratingActive: true, - orderId: true, - orderIdStatus: true, - kycRequested: true, - fundsBlocked: true, - - author: { - select: { - id: true, - name: true, - verified: true, - admin: true, - verifier: true, - createdAt: true, - displayName: true, - picture: true, - totalKarma: true, - spammer: true, - verifiedLink: true, - - serviceAffiliations: { - select: { - role: true, - service: { - select: { - name: true, - slug: true, - }, - }, - }, - }, - }, - }, - votes: { - select: { - userId: true, - downvote: true, - }, - }, - }, - orderBy: [{ suspicious: 'asc' }, { createdAt: 'desc' }], -} as const satisfies Prisma.CommentFindManyArgs - -export type CommentWithReplies = Record> = - Prisma.CommentGetPayload & - T & { - replies?: CommentWithReplies[] - } - -export type CommentWithRepliesPopulated = CommentWithReplies<{ - isWatchingReplies: boolean -}> - -export const commentSortSchema = z.enum(['newest', 'upvotes', 'status']).default('newest') -export type CommentSortOption = z.infer - -export function makeCommentsNestedQuery({ - depth = 0, - user, - showPending, - serviceId, - sort, -}: { - depth?: number - user: Prisma.UserGetPayload<{ - select: { - id: true - } - }> | null - showPending?: boolean - serviceId: number - sort: CommentSortOption -}) { - const orderByClause: Prisma.CommentOrderByWithRelationInput[] = [] - - switch (sort) { - case 'upvotes': - orderByClause.push({ upvotes: 'desc' }) - break - case 'status': - orderByClause.push({ status: 'asc' }) // PENDING, APPROVED, VERIFIED, REJECTED - break - case 'newest': // Default - default: - orderByClause.push({ createdAt: 'desc' }) - break - } - orderByClause.unshift({ suspicious: 'asc' }) // Always put suspicious comments last within a sort group - - const baseQuery = { - ...commentReplyQuery, - orderBy: orderByClause, - where: { - OR: [ - ...(user ? [{ authorId: user.id } as const satisfies Prisma.CommentWhereInput] : []), - showPending - ? ({ - status: { in: ['APPROVED', 'VERIFIED', 'PENDING', 'HUMAN_PENDING'] }, - } as const satisfies Prisma.CommentWhereInput) - : ({ - status: { in: ['APPROVED', 'VERIFIED'] }, - } as const satisfies Prisma.CommentWhereInput), - ], - parentId: null, - serviceId, - }, - } as const satisfies Prisma.CommentFindManyArgs - - if (depth <= 0) return baseQuery - - return { - ...baseQuery, - select: { - ...baseQuery.select, - replies: makeRepliesQuery(commentReplyQuery, depth - 1), - }, - } -} - -type RepliesQueryRecursive = - | T - | (Omit & { - select: Omit & { - replies: RepliesQueryRecursive - } - }) - -export function makeRepliesQuery( - query: T, - currentDepth: number -): RepliesQueryRecursive { - if (currentDepth <= 0) return query - - return { - ...query, - select: { - ...query.select, - replies: makeRepliesQuery(query, currentDepth - 1), - }, - } -} - -export function makeCommentUrl({ - serviceSlug, - commentId, - origin, -}: { - serviceSlug: string - commentId: number - origin: string -}) { - return `${origin}/service/${serviceSlug}?comment=${commentId.toString()}#comment-${commentId.toString()}` as const -} diff --git a/web/src/lib/contactMethods.ts b/web/src/lib/contactMethods.ts deleted file mode 100644 index d8239d8..0000000 --- a/web/src/lib/contactMethods.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { parsePhoneNumberWithError } from 'libphonenumber-js' - -type Formatter = { - id: string - matcher: RegExp - formatter: (value: string) => string | null -} -const formatters = [ - { - id: 'email', - matcher: /mailto:(.*)/, - formatter: (value) => value, - }, - { - id: 'telephone', - matcher: /tel:(.*)/, - formatter: (value) => { - return parsePhoneNumberWithError(value).formatInternational() - }, - }, - { - id: 'whatsapp', - matcher: /https?:\/\/wa\.me\/(.*)\/?/, - formatter: (value) => { - return parsePhoneNumberWithError(value).formatInternational() - }, - }, - { - id: 'telegram', - matcher: /https?:\/\/t\.me\/(.*)\/?/, - formatter: (value) => `t.me/${value}`, - }, - { - id: 'linkedin', - matcher: /https?:\/\/(?:www\.)?linkedin\.com\/(?:in|company)\/(.*)\/?/, - formatter: (value) => `in/${value}`, - }, - { - id: 'website', - matcher: /https?:\/\/(?:www\.)?((?:[a-zA-Z0-9-]+\.)+[a-zA-Z]+)/, - formatter: (value) => value, - }, -] as const satisfies Formatter[] - -export function formatContactMethod(url: string) { - for (const formatter of formatters) { - const captureGroup = url.match(formatter.matcher)?.[1] - if (!captureGroup) continue - - const formattedValue = formatter.formatter(captureGroup) - if (!formattedValue) continue - - return { - type: formatter.id, - formattedValue, - } as const - } - - return null -} diff --git a/web/src/lib/defineProtectedAction.ts b/web/src/lib/defineProtectedAction.ts deleted file mode 100644 index f23ac72..0000000 --- a/web/src/lib/defineProtectedAction.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { - ActionError, - defineAction, - type ActionAccept, - type ActionAPIContext, - type ActionHandler, -} from 'astro:actions' - -import type { MaybePromise } from 'astro/actions/runtime/utils.js' -import type { z } from 'astro/zod' - -type SpecialUserPermission = 'admin' | 'verified' | 'verifier' -type Permission = SpecialUserPermission | 'guest' | 'not-spammer' | 'user' - -type ActionAPIContextWithUser = ActionAPIContext & { - locals: { - user: NonNullable - } -} - -type ActionHandlerWithUser = TInputSchema extends z.ZodType - ? (input: z.infer, context: ActionAPIContextWithUser) => MaybePromise - : ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - input: any, - context: ActionAPIContextWithUser - ) => MaybePromise - -export function defineProtectedAction< - P extends Permission | SpecialUserPermission[], - TOutput, - TAccept extends ActionAccept | undefined = undefined, - TInputSchema extends z.ZodType | undefined = TAccept extends 'form' ? z.ZodType : undefined, ->({ - accept, - input: inputSchema, - handler, - permissions, -}: { - input?: TInputSchema - accept?: TAccept - handler: P extends 'guest' - ? ActionHandler - : ActionHandlerWithUser - permissions: P -}) { - return defineAction({ - accept, - input: inputSchema, - handler: ((input, context) => { - if (permissions === 'guest' || (Array.isArray(permissions) && permissions.length === 0)) { - return handler( - input, - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any - context as any - ) - } - - if (!context.locals.user) { - throw new ActionError({ - code: 'UNAUTHORIZED', - message: 'You must be logged in to perform this action.', - }) - } - - if (permissions === 'not-spammer' && context.locals.user.spammer) { - throw new ActionError({ - code: 'FORBIDDEN', - message: 'Spammer users are not allowed to perform this action.', - }) - } - - if ( - (permissions === 'verified' || (Array.isArray(permissions) && permissions.includes('verified'))) && - !context.locals.user.verified - ) { - if (context.locals.user.spammer) { - throw new ActionError({ - code: 'FORBIDDEN', - message: 'Spammer users are not allowed to perform this action.', - }) - } - throw new ActionError({ - code: 'FORBIDDEN', - message: 'Verified user privileges required.', - }) - } - - if ( - (permissions === 'verifier' || (Array.isArray(permissions) && permissions.includes('verifier'))) && - !context.locals.user.verifier - ) { - if (context.locals.user.spammer) { - throw new ActionError({ - code: 'FORBIDDEN', - message: 'Spammer users are not allowed to perform this action.', - }) - } - throw new ActionError({ - code: 'FORBIDDEN', - message: 'Verifier privileges required.', - }) - } - - if ( - (permissions === 'admin' || (Array.isArray(permissions) && permissions.includes('admin'))) && - !context.locals.user.admin - ) { - if (context.locals.user.spammer) { - throw new ActionError({ - code: 'FORBIDDEN', - message: 'Spammer users are not allowed to perform this action.', - }) - } - throw new ActionError({ - code: 'FORBIDDEN', - message: 'Admin privileges required.', - }) - } - - return handler(input, context as ActionAPIContextWithUser) - }) as ActionHandler, - }) -} diff --git a/web/src/lib/envVariables.ts b/web/src/lib/envVariables.ts deleted file mode 100644 index 36d7f48..0000000 --- a/web/src/lib/envVariables.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { z } from 'astro/zod' - -const schema = z.enum(['development', 'staging', 'production']) - -export const DEPLOYMENT_MODE = schema.parse(import.meta.env.PROD ? import.meta.env.MODE : 'development') diff --git a/web/src/lib/errorBanners.ts b/web/src/lib/errorBanners.ts deleted file mode 100644 index c2945b9..0000000 --- a/web/src/lib/errorBanners.ts +++ /dev/null @@ -1,228 +0,0 @@ -import type { APIContext, AstroGlobal } from 'astro' -import type { ErrorInferenceObject } from 'astro/actions/runtime/utils.js' -import type { ActionError, SafeResult } from 'astro:actions' - -type MessageObject = - | { - uiMessage: string - type: 'success' - origin: 'action' | 'runtime' | 'url' | `custom_${string}` - error?: undefined - } - | ({ - uiMessage: string - type: 'error' - } & ( - | { - origin: 'action' - // eslint-disable-next-line @typescript-eslint/no-explicit-any - error: ActionError - } - | { origin: 'runtime'; error?: unknown } - | { origin: 'url'; error?: undefined } - | { origin: `custom_${string}`; error?: unknown } - )) - -/** - * Helper class for handling errors and success messages. - * It automatically adds messages from the query string to the messages array. - * - * @example - * ```astro - * --- - * const messages = new ErrorBanners(Astro) - * const data = await messages.try('Oops!', () => fetchData()) - * --- - * - * {data ? data : 'No data'} - * - * ``` - */ -export class ErrorBanners { - private _messages: MessageObject[] = [] - - constructor(messages?: MessageObject[]) { - this._messages = messages ?? [] - } - - /** - * Get all stored messages. - */ - public get get() { - return [...this._messages] - } - - /** - * Get all error messages. - */ - public get errors() { - return this._messages.filter((msg) => msg.type === 'error').map((error) => error.uiMessage) - } - - /** - * Get all success messages. - */ - public get successes() { - return this._messages.filter((msg) => msg.type === 'success').map((success) => success.uiMessage) - } - - /** - * Handler for errors. Intended to be used in `.catch()` blocks. - * - * @example - * ```ts - * const data = await fetchData().catch(messages.handler('Oops!')) - * ``` - * - * @param uiMessage The UI message to display, or function to generate it. - * @returns The handler function. - */ - public handler(uiMessage: string | ((error: unknown) => string)) { - return (error: unknown) => { - const message = typeof uiMessage === 'function' ? uiMessage(error) : uiMessage - console.error(`[ErrorBanners] ${message}`, error) - this._messages.push({ - uiMessage: message, - type: 'error', - error, - origin: 'runtime', - }) - } - } - - /** - * Run a function and catch its errors. - * - * @example - * ```ts - * const items = await messages.try("Oops!", () => fetchItems()) // Item[] | undefined - * const items = await messages.try("Oops!", () => fetchItems(), []) // Item[] - * ``` - * - * @param uiMessage The UI message to display, or function to generate it. - * @param fn The function to execute. - * @param fallback The fallback value. - * @returns The result of the function. - */ - public async try( - uiMessage: string | ((error: unknown) => string), - fn: () => Promise | T, - fallback?: F - ) { - try { - const result = await fn() - return result - } catch (error) { - this.handler(uiMessage)(error) - return fallback as F - } - } - - /** - * Run multiple functions in parallel and catch errors. - * - * @example - * ```ts - * const [categories, posts] = await messages.tryMany([ - * ["Error in categories", () => fetchCategories(), []], - * ["Error in posts", () => fetchPosts()] - * ]) - * ``` - * - * @param operations The operations to run. - * @returns The results of the operations. - */ - public async tryMany>[] | []>( - operations: T - ) { - const results = await Promise.all(operations.map((args) => this.try(...args))) - - return results as unknown as Promise<{ - -readonly [P in keyof T]: Awaited> | T[P][2] - }> - } - - /** - * Add one or multiple messages. - */ - public add(...messages: (MessageObject | null | undefined)[]) { - messages - .filter((message) => message !== null && message !== undefined) - .forEach((message) => { - this._messages.push(message) - if (message.type === 'error') { - console.error(`[ErrorBanners] ${message.uiMessage}`, message.error) - } - }) - } - - /** - * Add a success message. - */ - public addSuccess(message: string) { - this._messages.push({ - uiMessage: message, - type: 'success', - origin: 'runtime', - }) - } - - /** - * Add a success message if the action result is successful. - */ - public addIfSuccess( - actionResult: SafeResult | undefined, - message: string | ((actionResult: TOutput) => string) - ) { - if (actionResult && !actionResult.error) { - const actualMessage = typeof message === 'function' ? message(actionResult.data) : message - this._messages.push({ - uiMessage: actualMessage, - type: 'success', - origin: 'action', - }) - } - } - - /** - * Clear all messages. - */ - public clear() { - this._messages = [] - } -} - -/** - * Generate message objects for {@link ErrorBanners} from the URL. - */ -export function getMessagesFromUrl( - astro: Pick | Readonly, 'url'> -) { - const messages: MessageObject[] = [] - - // Get error messages - messages.push( - ...astro.url.searchParams.getAll('error').map( - (error) => - ({ - uiMessage: error, - type: 'error', - origin: 'url', - }) as const satisfies MessageObject - ) - ) - - // Get success messages - messages.push( - ...astro.url.searchParams.getAll('success').map( - (success) => - ({ - uiMessage: success, - type: 'success', - origin: 'url', - }) as const satisfies MessageObject - ) - ) - - return messages -} diff --git a/web/src/lib/fileStorage.ts b/web/src/lib/fileStorage.ts deleted file mode 100644 index 4b6e90e..0000000 --- a/web/src/lib/fileStorage.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { createHash } from 'crypto' -import fs from 'node:fs/promises' -import path from 'node:path' - -import { UPLOAD_DIR } from 'astro:env/server' - -/** - * Get the configured upload directory with a subdirectory - */ -function getUploadDir(subDir = ''): { fsPath: string; webPath: string } { - // Get the base upload directory from environment variable - let baseUploadDir = UPLOAD_DIR - - // Determine if the path is absolute or relative - const isAbsolutePath = path.isAbsolute(baseUploadDir) - - // If it's a relative path, resolve it relative to the project root - if (!isAbsolutePath) { - baseUploadDir = path.join(process.cwd(), baseUploadDir) - } - - // For the filesystem path, combine the base dir with the subdirectory - const fsPath = path.join(baseUploadDir, subDir) - - // For dynamic uploads, use the endpoint URL - let webPath = `/files${subDir ? `/${subDir}` : ''}` - - // Normalize paths to ensure proper formatting - webPath = path.normalize(webPath).replace(/\\/g, '/') - webPath = sanitizePath(webPath) - - return { - fsPath: path.normalize(fsPath), - webPath, - } -} - -/** - * Generate a hash from file content - */ -async function generateFileHash(file: File): Promise { - const buffer = await file.arrayBuffer() - const hash = createHash('sha1') - hash.update(Buffer.from(buffer)) - return hash.digest('hex').substring(0, 10) // Use first 10 chars of hash -} - -/** - * Save a file locally and return its web-accessible URL path - */ -export async function saveFileLocally( - file: File, - originalFileName: string, - subDir?: string -): Promise { - const fileBuffer = await file.arrayBuffer() - const fileHash = await generateFileHash(file) - - const fileExtension = path.extname(originalFileName) - const fileName = `${fileHash}${fileExtension}` - - // Use the provided subDir or default to 'services/pictures' - const { fsPath: uploadDir, webPath: webUploadPath } = getUploadDir(subDir ?? 'services/pictures') - - await fs.mkdir(uploadDir, { recursive: true }) - const filePath = path.join(uploadDir, fileName) - await fs.writeFile(filePath, Buffer.from(fileBuffer)) - const url = sanitizePath(`${webUploadPath}/${fileName}`) - return url -} - -function sanitizePath(inputPath: string): string { - let sanitized = inputPath.replace(/\\+/g, '/') - // Collapse multiple slashes, but preserve protocol (e.g., http://) - sanitized = sanitized.replace(/([^:])\/+/g, '$1/') - sanitized = sanitized.replace(/\/(\?|#|$)/g, '$1') - return sanitized -} diff --git a/web/src/lib/formInputs.ts b/web/src/lib/formInputs.ts deleted file mode 100644 index 9c940a2..0000000 --- a/web/src/lib/formInputs.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const baseInputClassNames = { - input: - 'bg-night-600 block placeholder:text-sm placeholder:text-day-600 text-day-100 w-full min-w-0 rounded-lg border border-night-400 px-3 leading-none h-9', - div: 'bg-night-600 rounded-lg border border-night-400 text-sm', - error: 'border-red-500 focus:border-red-500 focus:ring-red-500', - disabled: 'cursor-not-allowed', - textarea: 'resize-y min-h-16', - file: 'file:bg-day-700 file:text-day-100 hover:file:bg-day-600 file:mr-4 file:rounded-md file:border-0 file:px-4 file:py-2 file:text-sm file:font-medium h-12 p-1.25', -} as const satisfies Record diff --git a/web/src/lib/honeypot.ts b/web/src/lib/honeypot.ts deleted file mode 100644 index 70fd606..0000000 --- a/web/src/lib/honeypot.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { ActionError } from 'astro:actions' - -import { prisma } from './prisma' - -export const handleHoneypotTrap = async >({ - input, - honeyPotTrapField, - userId, - location, - dontMarkAsSpammer = false, -}: { - input: T - honeyPotTrapField: keyof T - userId: number | null | undefined - location: string - dontMarkAsSpammer?: boolean -}) => { - if (!input[honeyPotTrapField]) return - - if (!dontMarkAsSpammer && !!userId) { - await prisma.user.update({ - where: { - id: userId, - }, - data: { - spammer: true, - internalNotes: { - create: { - content: `Marked as spammer because it fell for the honey pot trap in: ${location}`, - }, - }, - }, - }) - } - - throw new ActionError({ - message: 'Invalid request', - code: 'BAD_REQUEST', - }) -} diff --git a/web/src/lib/impersonation.ts b/web/src/lib/impersonation.ts deleted file mode 100644 index 2bd4e2e..0000000 --- a/web/src/lib/impersonation.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { redisImpersonationSessions } from './redis/redisImpersonationSessions' - -import type { APIContext, AstroCookies } from 'astro' - -const IMPERSONATION_SESSION_COOKIE = 'impersonation_session_id' - -export async function startImpersonating( - context: Pick, - adminUser: NonNullable, - targetUser: NonNullable -) { - const sessionId = await redisImpersonationSessions.store({ - adminId: adminUser.id, - targetId: targetUser.id, - }) - - context.cookies.set(IMPERSONATION_SESSION_COOKIE, sessionId, { - path: '/', - secure: true, - httpOnly: true, - sameSite: 'strict', - maxAge: redisImpersonationSessions.expirationTime, - }) - context.locals.user = targetUser - context.locals.actualUser = adminUser -} - -export async function stopImpersonating(context: Pick) { - const sessionId = context.cookies.get(IMPERSONATION_SESSION_COOKIE)?.value - await redisImpersonationSessions.delete(sessionId) - context.cookies.delete(IMPERSONATION_SESSION_COOKIE) - context.locals.user = context.locals.actualUser - context.locals.actualUser = null -} - -export async function getImpersonationInfo(cookies: AstroCookies) { - const sessionId = cookies.get(IMPERSONATION_SESSION_COOKIE)?.value - return await redisImpersonationSessions.get(sessionId) -} diff --git a/web/src/lib/karmaUnlocks.ts b/web/src/lib/karmaUnlocks.ts deleted file mode 100644 index 8c1c92a..0000000 --- a/web/src/lib/karmaUnlocks.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { karmaUnlocksById, type KarmaUnlockInfo } from '../constants/karmaUnlocks' - -export type KarmaUnlocks = { - [K in keyof typeof karmaUnlocksById]: boolean -} - -export function computeKarmaUnlocks(karma: number) { - return Object.fromEntries( - Object.entries(karmaUnlocksById).map(([key, value]) => [ - key, - value.karma >= 0 ? karma >= value.karma : karma <= value.karma, - ]) - ) as KarmaUnlocks -} - -export function makeUserWithKarmaUnlocks(user: null): null -export function makeUserWithKarmaUnlocks( - user: T -): T & { karmaUnlocks: KarmaUnlocks } -export function makeUserWithKarmaUnlocks( - user: T | null -): (T & { karmaUnlocks: KarmaUnlocks }) | null -export function makeUserWithKarmaUnlocks(user: T | null) { - return user ? { ...user, karmaUnlocks: computeKarmaUnlocks(user.totalKarma) } : null -} - -export function makeKarmaUnlockMessage(karmaUnlock: KarmaUnlockInfo) { - return `You need ${karmaUnlock.karma.toLocaleString()} karma to ${karmaUnlock.verb}.` as const -} diff --git a/web/src/lib/makeHelpersForOptions.ts b/web/src/lib/makeHelpersForOptions.ts deleted file mode 100644 index 38714f6..0000000 --- a/web/src/lib/makeHelpersForOptions.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { uniqBy } from 'lodash-es' - -import { zodEnumFromConstant } from './arrays' -import { typedGroupBy, type TypedGroupBy } from './objects' - -import type { ZodEnum } from 'astro/zod' - -/** - * Creates utility functions to work with an array of options. - * Primarily a `getFn` and `useGetHook`, that return the option object based on the key, or a fallback value if the key is not found. - * - * @param dataArray - Array of objects, must be defined using `as const` to ensure type safety. - * @param key - The key to group the array by - */ -export function makeHelpersForOptions< - K extends string, - Fallback extends Record & - Record & { slug?: string | null | undefined }, - TArray extends readonly (Fallback & Record)[], - HasSlugs extends boolean = TArray extends Record<'slug', string>[] ? true : false, ->(key: K, makeFallback: (key: string | null | undefined) => Fallback, dataArray: TArray) { - const hasDuplicateIds = uniqBy(dataArray, key).length !== dataArray.length - if (hasDuplicateIds) { - throw new Error(`[makeHelpersForOptions] Duplicate ${key} in dataArray`) - } - - const hasSlugs = dataArray.some((item) => 'slug' in item && typeof item.slug === 'string') - const allSlugsAreDefined = dataArray.every((item) => 'slug' in item && typeof item.slug === 'string') - if (hasSlugs) { - if (!allSlugsAreDefined) { - throw new Error('[makeHelpersForOptions] Some slugs are missing in dataArray') - } - - const hasDuplicateSlugs = uniqBy(dataArray, 'slug').length !== dataArray.length - if (hasDuplicateSlugs) { - throw new Error('[makeHelpersForOptions] Duplicate slug in dataArray') - } - } - - const dataObject = typedGroupBy(dataArray, key) - const dataObjectBySlug = ( - allSlugsAreDefined - ? typedGroupBy(dataArray as TArray extends Record<'slug', string>[] ? TArray : never, 'slug') - : undefined - ) as HasSlugs extends true - ? TypedGroupBy<'slug', TArray extends Record<'slug', string>[] ? TArray[number] : never> - : undefined - - function getFn(id: T): Extract> - function getFn(id: T): Extract> | Fallback - function getFn( - id: T - ): Extract> | Fallback { - return typeof id === 'string' && id in dataObject - ? dataObject[id as unknown as keyof typeof dataObject] - : makeFallback(id) - } - - function getFnSlug(slug: T): Extract> - function getFnSlug( - slug: T - ): Extract> | Fallback - function getFnSlug( - slug: T - ): Extract> | Fallback { - return typeof slug === 'string' && dataObjectBySlug && slug in dataObjectBySlug - ? (dataObjectBySlug as NonNullable)[ - slug as unknown as keyof NonNullable - ] - : makeFallback(null) - } - - // const useGetHook: typeof getFn = ((status: any) => { - // return useMemo(() => getFn(status), [status]) - // }) as typeof getFn - - const exposedMakeFallback = , K>>( - id: Parameters[0], - options?: O - ) => { - return { - ...makeFallback(id), - ...options, - } as Fallback & O - } - - const zodEnumById = zodEnumFromConstant(dataArray, key) - const zodEnumBySlug = ( - allSlugsAreDefined - ? zodEnumFromConstant(dataArray as TArray extends Record<'slug', string>[] ? TArray : never, 'slug') - : undefined - ) as HasSlugs extends true ? ZodEnum<[TArray[number]['slug'], ...TArray[number]['slug'][]]> : undefined - - function slugToKey(slug: T): Extract>[K] - function slugToKey( - slug: T - ): Extract>[K] | undefined - function slugToKey( - slug: T - ): Extract>[K] | undefined { - return typeof slug === 'string' && dataObjectBySlug && slug in dataObjectBySlug - ? ((dataObjectBySlug as NonNullable)[ - slug as unknown as keyof NonNullable - ][key] as unknown as Extract>[K]) - : undefined - } - - function keyToSlug(slug: T): Extract>['slug'] - function keyToSlug( - slug: T - ): Extract>['slug'] | undefined - function keyToSlug( - slug: T - ): Extract>['slug'] | undefined { - return typeof slug === 'string' && slug in dataObject - ? (dataObject[slug as unknown as keyof NonNullable][key] as unknown as Extract< - TArray[number], - Record - >['slug']) - : undefined - } - - return { - dataArray, - dataObject, - /** Gets the info by key, if not found, returns a fallback value */ - getFn, - /** Gets the info by key, if not found, returns a fallback value */ - // useGetHook: useGetHook, - /** Generates a fallback value */ - makeFallback: exposedMakeFallback, - zodEnumById, - - dataObjectBySlug, - /** Gets the info by slug, if not found, returns a fallback value */ - getFnSlug, - zodEnumBySlug, - - /** Gets the id by slug, if not found, returns undefined */ - slugToKey, - /** Gets the slug by id, if not found, returns undefined */ - keyToSlug, - } -} diff --git a/web/src/lib/markdown.ts b/web/src/lib/markdown.ts deleted file mode 100644 index f7b5c47..0000000 --- a/web/src/lib/markdown.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** A string containing Markdown. */ -export type MarkdownString = string - -/** A string containing HTML. */ -export type HtmlString = string diff --git a/web/src/lib/notificationPreferences.ts b/web/src/lib/notificationPreferences.ts deleted file mode 100644 index 5e3e327..0000000 --- a/web/src/lib/notificationPreferences.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { prisma } from './prisma' - -import type { Prisma } from '@prisma/client' - -export async function getOrCreateNotificationPreferences( - userId: number, - select: { [K in keyof T]: K extends keyof Prisma.NotificationPreferencesSelect ? T[K] : never }, - tx: Prisma.TransactionClient = prisma -) { - return ( - (await tx.notificationPreferences.findUnique({ where: { userId }, select })) ?? - (await tx.notificationPreferences.create({ data: { userId }, select })) - ) -} diff --git a/web/src/lib/notifications.ts b/web/src/lib/notifications.ts deleted file mode 100644 index 8ced794..0000000 --- a/web/src/lib/notifications.ts +++ /dev/null @@ -1,295 +0,0 @@ -import { accountStatusChangesById } from '../constants/accountStatusChange' -import { commentStatusChangesById } from '../constants/commentStatusChange' -import { eventTypesById } from '../constants/eventTypes' -import { serviceVerificationStatusChangesById } from '../constants/serviceStatusChange' -import { serviceSuggestionStatusChangesById } from '../constants/suggestionStatusChange' - -import { makeCommentUrl } from './commentsWithReplies' - -import type { Prisma } from '@prisma/client' - -export function makeNotificationTitle( - notification: Prisma.NotificationGetPayload<{ - select: { - type: true - aboutAccountStatusChange: true - aboutCommentStatusChange: true - aboutServiceVerificationStatusChange: true - aboutSuggestionStatusChange: true - aboutComment: { - select: { - author: { select: { id: true } } - status: true - parent: { - select: { - author: { - select: { - id: true - } - } - } - } - service: { - select: { - name: true - } - } - } - } - aboutServiceSuggestion: { - select: { - status: true - service: { - select: { - name: true - } - } - } - } - aboutServiceSuggestionMessage: { - select: { - suggestion: { - select: { - service: { - select: { - name: true - } - } - } - } - } - } - aboutEvent: { - select: { - type: true - service: { - select: { - name: true - } - } - } - } - aboutService: { - select: { - name: true - verificationStatus: true - } - } - } - }>, - user: Prisma.UserGetPayload<{ select: { id: true } }> | null -): string { - switch (notification.type) { - case 'COMMENT_STATUS_CHANGE': { - if (!notification.aboutComment) return 'A comment you are watching had a status change' - - if (!notification.aboutCommentStatusChange) { - return `Comment on ${notification.aboutComment.service.name} had a status change` - } - const statusChange = commentStatusChangesById[notification.aboutCommentStatusChange] - const serviceName = notification.aboutComment.service.name - const isOwnComment = !!user && notification.aboutComment.author.id === user.id - const prefix = isOwnComment ? 'Your comment' : 'Watched comment' - - return `${prefix} on ${serviceName} ${statusChange.notificationTitle}` - } - case 'REPLY_COMMENT_CREATED': { - if (!notification.aboutComment) return 'You have a new reply' - const serviceName = notification.aboutComment.service.name - if (!notification.aboutComment.parent) { - return `New reply to a comment on ${serviceName}` - } - const isOwnParentComment = !!user && notification.aboutComment.parent.author.id === user.id - return isOwnParentComment - ? `New reply to your comment on ${serviceName}` - : `New reply to a watched comment on ${serviceName}` - } - case 'COMMUNITY_NOTE_ADDED': { - if (!notification.aboutComment) return 'A community note was added' - const serviceName = notification.aboutComment.service.name - const isOwnComment = !!user && notification.aboutComment.author.id === user.id - return isOwnComment - ? `Community note added to your comment on ${serviceName}` - : `Community note added to a watched comment on ${serviceName}` - } - case 'ROOT_COMMENT_CREATED': { - if (!notification.aboutComment) return 'New comment' - const service = notification.aboutComment.service.name - return notification.aboutComment.status == 'PENDING' - ? `New unmoderated comment on ${service}` - : `New comment on ${service}` - } - case 'SUGGESTION_MESSAGE': { - if (!notification.aboutServiceSuggestionMessage) return 'New message on your suggestion' - const service = notification.aboutServiceSuggestionMessage.suggestion.service.name - return `New message for ${service} suggestion` - } - case 'SUGGESTION_STATUS_CHANGE': { - if (!notification.aboutServiceSuggestion) return 'Suggestion status updated' - const service = notification.aboutServiceSuggestion.service.name - if (!notification.aboutSuggestionStatusChange) { - return `${service} suggestion status updated` - } - const statusChange = serviceSuggestionStatusChangesById[notification.aboutSuggestionStatusChange] - return `${service} suggestion ${statusChange.notificationTitle}` - } - // TODO: [KARMA_UNLOCK] Will be added later, when karma unloks are in the database, not in the code. - // case 'KARMA_UNLOCK': { - // return 'New karma level unlocked' - // } - case 'ACCOUNT_STATUS_CHANGE': { - if (!notification.aboutAccountStatusChange) return 'Your account status has been updated' - const accountStatusChange = accountStatusChangesById[notification.aboutAccountStatusChange] - return accountStatusChange.notificationTitle - } - case 'EVENT_CREATED': { - if (!notification.aboutEvent) return 'New event on a service' - const service = notification.aboutEvent.service.name - const eventType = eventTypesById[notification.aboutEvent.type].label - return `${eventType} event on ${service}` - } - case 'SERVICE_VERIFICATION_STATUS_CHANGE': { - if (!notification.aboutService) return 'Service verification status updated' - const serviceName = notification.aboutService.name - if (!notification.aboutServiceVerificationStatusChange) { - return `${serviceName} verification status updated` - } - const statusChange = - serviceVerificationStatusChangesById[notification.aboutServiceVerificationStatusChange] - return `${serviceName} ${statusChange.notificationTitle}` - } - } -} - -export function makeNotificationContent( - notification: Prisma.NotificationGetPayload<{ - select: { - type: true - aboutComment: { - select: { - content: true - communityNote: true - } - } - aboutServiceSuggestionMessage: { - select: { - content: true - } - } - aboutEvent: { - select: { - title: true - } - } - } - }> -): string | null { - switch (notification.type) { - // TODO: [KARMA_UNLOCK] Will be added later, when karma unloks are in the database, not in the code. - // case 'KARMA_UNLOCK': - case 'SUGGESTION_STATUS_CHANGE': - case 'ACCOUNT_STATUS_CHANGE': - case 'SERVICE_VERIFICATION_STATUS_CHANGE': { - return null - } - case 'COMMENT_STATUS_CHANGE': - case 'REPLY_COMMENT_CREATED': - case 'ROOT_COMMENT_CREATED': { - if (!notification.aboutComment) return null - return notification.aboutComment.content - } - case 'COMMUNITY_NOTE_ADDED': { - if (!notification.aboutComment) return null - return notification.aboutComment.communityNote - } - case 'SUGGESTION_MESSAGE': { - if (!notification.aboutServiceSuggestionMessage) return null - return notification.aboutServiceSuggestionMessage.content - } - case 'EVENT_CREATED': { - if (!notification.aboutEvent) return null - return notification.aboutEvent.title - } - } -} - -export function makeNotificationLink( - notification: Prisma.NotificationGetPayload<{ - select: { - type: true - aboutComment: { - select: { - id: true - service: { - select: { - slug: true - } - } - } - } - aboutServiceSuggestionId: true - aboutServiceSuggestionMessage: { - select: { - id: true - suggestion: { - select: { - id: true - } - } - } - } - aboutEvent: { - select: { - service: { - select: { - slug: true - } - } - } - } - aboutService: { - select: { - slug: true - } - } - } - }>, - origin: string -): string | null { - switch (notification.type) { - case 'COMMENT_STATUS_CHANGE': - case 'REPLY_COMMENT_CREATED': - case 'COMMUNITY_NOTE_ADDED': - case 'ROOT_COMMENT_CREATED': { - if (!notification.aboutComment) return null - return makeCommentUrl({ - serviceSlug: notification.aboutComment.service.slug, - commentId: notification.aboutComment.id, - origin, - }) - } - case 'SUGGESTION_MESSAGE': { - if (!notification.aboutServiceSuggestionMessage) return null - return `${origin}/service-suggestion/${String(notification.aboutServiceSuggestionMessage.suggestion.id)}#message-${String(notification.aboutServiceSuggestionMessage.id)}` - } - case 'SUGGESTION_STATUS_CHANGE': { - if (!notification.aboutServiceSuggestionId) return null - return `${origin}/service-suggestion/${String(notification.aboutServiceSuggestionId)}` - } - // TODO: [KARMA_UNLOCK] Will be added later, when karma unloks are in the database, not in the code. - // case 'KARMA_UNLOCK': { - // return `${origin}/account#karma-unlocks` - // } - case 'ACCOUNT_STATUS_CHANGE': { - return `${origin}/account#account-status` - } - case 'EVENT_CREATED': { - if (!notification.aboutEvent) return null - return `${origin}/service/${notification.aboutEvent.service.slug}#events` - } - case 'SERVICE_VERIFICATION_STATUS_CHANGE': { - if (!notification.aboutService) return null - return `${origin}/service/${notification.aboutService.slug}#verification` - } - } -} diff --git a/web/src/lib/numbers.ts b/web/src/lib/numbers.ts deleted file mode 100644 index 74cea85..0000000 --- a/web/src/lib/numbers.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { round } from 'lodash-es' - -export function parseIntWithFallback(value: unknown, fallback: F = null as F) { - const parsed = Number(value) - if (!Number.isInteger(parsed)) return fallback - return parsed -} - -/** - * Interpolates a value between a start and end value. - * @param value - The value to interpolate. - * @param start - The start value. - * @param end - The end value. - * @returns The interpolated value. - */ -export function interpolate(value: number, start: number, end: number) { - return start + (end - start) * value -} - -export type FormatNumberOptions = Intl.NumberFormatOptions & { - roundDigits?: number - showSign?: boolean - removeTrailingZeros?: boolean -} - -export function formatNumber( - value: number, - { roundDigits = 0, showSign = true, removeTrailingZeros = true, ...formatOptions }: FormatNumberOptions = {} -) { - const rounded = round(value, roundDigits) - const formatted = rounded.toLocaleString(undefined, formatOptions) - const withoutTrailingZeros = removeTrailingZeros ? formatted.replace(/\.0+$/, '') : formatted - const withSign = showSign && value > 0 ? `+${withoutTrailingZeros}` : withoutTrailingZeros - return withSign -} diff --git a/web/src/lib/objects.ts b/web/src/lib/objects.ts deleted file mode 100644 index 7a593ec..0000000 --- a/web/src/lib/objects.ts +++ /dev/null @@ -1,164 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { isEqualWith } from 'lodash-es' - -import { areEqualArraysWithoutOrder } from './arrays' - -import type { Prettify } from 'ts-essentials' -import type TB from 'ts-toolbelt' - -export function removeUndefined(obj: Record) { - return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined)) -} - -type RemoveUndefinedProps> = { - [key in keyof T]-?: Exclude -} - -/** - * Assigns properties from `obj2` to `obj1`, but only if they are defined in `obj2`. - * @example - * assignDefinedOnly({ a: 1, b: 2}, { a: undefined, b: 3, c: 4 }) // result = { a: 1, b: 3, c: 4 } - */ -export const assignDefinedOnly = , T2 extends Record>( - obj1: T1, - obj2: T2 -) => { - return { ...obj1, ...removeUndefined(obj2) } as Omit, keyof T1> & - Omit & { - [key in keyof T1 & keyof T2]-?: undefined extends T2[key] - ? Exclude | T1[key] - : T2[key] - } -} - -export type Paths = T extends object - ? { - [K in keyof T]: `${Exclude}${'' | `.${Paths}`}` - }[keyof T] - : never - -export type PathValue = TB.Object.Path> - -export type Leaves = T extends object - ? { - [K in keyof T]: `${Exclude}${Leaves extends never ? '' : `.${Leaves}`}` - }[keyof T] - : never - -// Start of paths with nested -type Digit = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 -type NextDigit = [1, 2, 3, 4, 5, 6, 7, 'STOP'] -type Inc = T extends Digit ? NextDigit[T] : 'STOP' -type StringOrNumKeys = TObj extends unknown[] ? 0 : string & keyof TObj -type NestedPath = TValue extends object - ? `${Prefix}.${TDepth extends 'STOP' ? string : NestedFieldPaths}` - : never -type GetValue = T extends unknown[] - ? K extends number - ? T[K] - : never - : K extends keyof T - ? T[K] - : never -type NestedFieldPaths = { - [TKey in StringOrNumKeys]: - | NestedPath, `${TKey}`, TValue, Inc> - | (GetValue extends TValue ? `${TKey}` : never) -}[StringOrNumKeys] -export type PathsWithNested = TData extends any ? NestedFieldPaths : never -// End of paths with nested - -export type TypedGroupBy & Record> = { - [Id in T[K]]: Extract> -} - -/** - * Converts an array of objects to an object with the key as the id and the value as the object. - * @example - * typedGroupBy([ - * { id: 'a', name: 'Letter A' }, - * { id: 'b', name: 'Letter B' } - * ] as const, 'id') - * // result = { - * // a: { id: 'a', name: 'Letter A' }, - * // b: { id: 'b', name: 'Letter B' } - * // } - */ -export const typedGroupBy = & Record>( - array: T[] | readonly T[], - key: K -) => { - return Object.fromEntries(array.map((option) => [option[key], option])) as TypedGroupBy -} - -/** - * Merges two objects, so that each property is the union of that property from each object. - * - If a key is present in only one of the objects, it becomes optional. - * - If an object is undefined, the other object is returned. - * - * To {@link UnionizeTwo} more than two objects, use {@link Unionize}. - * - * @example - * UnionizeTwo<{ a: 1, shared: 1 }, { b: 2, shared: 2 }> // { a?: 1, b?: 2, shared: 1 | 2 } - */ -export type UnionizeTwo< - T1 extends Record | undefined, - T2 extends Record | undefined, -> = keyof T1 extends undefined - ? T2 - : keyof T2 extends undefined - ? T1 - : { - [K in Exclude]+?: - | (K extends keyof T1 ? T1[K] : never) - | (K extends keyof T2 ? T2[K] : never) - } & { - [K in keyof T1 & keyof T2]-?: T1[K] | T2[K] - } - -/** - * Merges multiple objects, so that each property is the union of that property from each object. - * - If a key is present in only one of the objects, it becomes optional. - * - If an object is undefined, it is ignored. - * - If no objects are provided, `undefined` is returned. - * - * Internally, it uses {@link UnionizeTwo} recursively. - * - * @example - * Unionize<[ - * { a: 1, shared: 1 }, - * { b: 2, shared: 2 }, - * { a: 3, shared: 3 } - * ]> - * // result = { - * // a?: 1 | 3, - * // b?: 2, - * // shared: 1 | 2 | 3 - * // } - */ -export type Unionize[]> = Prettify< - T extends [] - ? undefined - : T extends [infer First, ...infer Rest] - ? Rest extends Record[] - ? First extends Record - ? UnionizeTwo> - : undefined - : First - : undefined -> - -/** - * Checks if two objects are equal without considering order in arrays. - * @example - * areEqualObjectsWithoutOrder({ a: [1, 2], b: 3 }, { b: 3, a: [2, 1] }) // true - */ -export function areEqualObjectsWithoutOrder>( - a: T, - b: Record -): b is T { - return isEqualWith(a, b, (a, b) => { - if (Array.isArray(a) && Array.isArray(b)) return areEqualArraysWithoutOrder(a, b) - return undefined - }) -} diff --git a/web/src/lib/onload.ts b/web/src/lib/onload.ts deleted file mode 100644 index f0d7ebc..0000000 --- a/web/src/lib/onload.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { Tail } from 'ts-essentials' - -export function addOnLoadEventListener( - ...args: Tail>> -) { - document.addEventListener('astro:page-load', ...args) - document.addEventListener('htmx:afterSwap', ...args) -} diff --git a/web/src/lib/parseUrlFilters.ts b/web/src/lib/parseUrlFilters.ts deleted file mode 100644 index 1b1f37f..0000000 --- a/web/src/lib/parseUrlFilters.ts +++ /dev/null @@ -1,311 +0,0 @@ -import { z } from 'astro/zod' -import { isEqual, omit } from 'lodash-es' - -import { areEqualObjectsWithoutOrder } from './objects' -import { getObjectSearchParam, makeObjectSearchParamKeyRegex } from './urls' - -import type { APIContext, AstroGlobal } from 'astro' -import type { ZodError, ZodType, ZodTypeDef } from 'astro/zod' - -type MyZodUnknown = ZodType< - Output, - Def, - Input -> - -type ZodParseFromUrlOptions = { - allOptional?: boolean -} - -/** - * Parses an array of values from a URL with zod. - * - * The wrong values are skipped, and the errors are returned. - * - * @example - * ```ts - * const schema = z.array(z.enum(['S', 'M', 'L', 'XL'])) - * const urlValue = ['wrong', 'M', 'L'] - * const { data, errors } = zodParseArray(schema, urlValue) - * // data: ['M', 'L'] - * // errors: [{ key: 0, error: ZodError }] - * ``` - */ -function zodParseArray(schema: T, urlValue: string[] | readonly string[]) { - const unwrappedSchema = unwrapSchema(schema, { - default: true, - optional: true, - nullable: true, - }) - const itemSchema = - unwrappedSchema instanceof z.ZodArray ? (unwrappedSchema as z.ZodArray).element : undefined - - if (!itemSchema || urlValue.length === 0) { - const parsedArray = schema.safeParse( - schema instanceof z.ZodDefault && urlValue.length === 0 ? undefined : urlValue - ) - return parsedArray.success - ? { - data: parsedArray.data, - errors: [], - } - : { - data: schema instanceof z.ZodOptional ? undefined : [], - errors: [{ key: 0, error: parsedArray.error }], - } - } - - const parsedItems = urlValue.map((item) => itemSchema.safeParse(item)) - - return { - data: parsedItems.filter((parsed) => parsed.success).map((r) => r.data), - errors: parsedItems.filter((parsed) => !parsed.success).map((r, i) => ({ key: i, error: r.error })), - } -} - -/** - * Parses the query params of a URL with zod. - * - * The wrong values are set to `undefined`, and the errors are returned. - * - * @example - * ```ts - * const params = new URLSearchParams('sizes=M&sizes=L&max-price=wrong') - * const schema = { - * sizes: z.array(z.enum(['S', 'M', 'L', 'XL'])), - * 'max-price': z.coerce.number(), - * 'min-price': z.coerce.number().default(0), - * } - * const { data, errors } = zodParseQueryParams(schema, params) - * // data: - * // { - * // sizes: ['M', 'L'], - * // 'max-price': undefined, - * // 'min-price': 0 - * // } - * // errors: [{ key: 'max-price', error: ZodError }] - * ``` - */ -export function zodParseQueryParams, O extends ZodParseFromUrlOptions>( - shape: T, - params: URLSearchParams, - options?: O -) { - const errors: { key: string; error: ZodError }[] = [] - - const data = Object.fromEntries( - Object.entries(shape).map(([key, paramSchema]) => { - const schema = - !(paramSchema instanceof z.ZodDefault || paramSchema instanceof z.ZodEffects) && - options?.allOptional !== false - ? paramSchema.optional() - : paramSchema - const unwrappedSchema = unwrapSchema(schema, { - default: true, - optional: true, - nullable: true, - }) - - if (unwrappedSchema instanceof z.ZodArray) { - const parsed = zodParseArray(schema, params.getAll(key)) - const firstError = parsed.errors[0] - if (firstError) errors.push({ key, error: firstError.error }) - - return [key, parsed.data] - } - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const urlStringValue = params.get(key) || undefined - const urlValue = - unwrappedSchema instanceof z.ZodArray - ? params.getAll(key) - : unwrappedSchema instanceof z.ZodObject || unwrappedSchema instanceof z.ZodRecord - ? getObjectSearchParam(params, key) - : urlStringValue - - const parsed = schema.safeParse(urlValue) - if (!parsed.success) { - errors.push({ key, error: parsed.error }) - return [key, paramSchema.safeParse(undefined).data] - } - - return [key, parsed.data] - }) - ) as { - [K in keyof T]: ReturnType< - (O['allOptional'] extends false - ? T[K] - : // eslint-disable-next-line @typescript-eslint/no-explicit-any - T[K] extends z.ZodArray | z.ZodDefault | z.ZodEffects - ? T[K] - : z.ZodOptional)['parse'] - > - } - - return { data, errors } -} - -type CleanUrlOptions = { - removeUneededObjectParams?: boolean - removeParams?: { - [K in T]?: { if: 'another-is-unset'; prop: K } | { if: 'default' } - } -} - -/** - * Parses the query params of the current URL with zod and stores the errors in the context. - * - * Wrong values are set to `undefined`, and the errors stored in `Astro.locals.banners`. - * - * @example - * ```ts - * const schema = { - * sizes: z.array(z.enum(['S', 'M', 'L', 'XL'])), - * 'max-price': z.coerce.number(), - * 'min-price': z.coerce.number().default(0), - * } - * const data = zodParseQueryParamsStoringErrors(schema, Astro) - * // data: - * // { - * // sizes: ['M', 'L'], - * // 'max-price': undefined, - * // 'min-price': 0 - * // } - * // And 1 error stored in Astro.locals.banners (`max-price`). - * ``` - */ -export function zodParseQueryParamsStoringErrors< - K extends string, - T extends Record, - O extends ZodParseFromUrlOptions & { - ignoredKeysForDefaultData?: K[] - cleanUrl?: CleanUrlOptions - }, - C extends Pick | Readonly, 'locals' | 'url'>, ->(shape: T, context: C, options?: O) { - const { data, errors } = zodParseQueryParams(shape, context.url.searchParams, options) - context.locals.banners.add( - ...errors.map( - (error) => - ({ - uiMessage: `Error in the ${error.key} filter. Using default value.`, - type: 'error', - error: error.error, - origin: 'custom_filters', - }) as const - ) - ) - - const defaultDataWithoutIgnoringKeys = zodParseQueryParams(shape, new URLSearchParams(), options).data - const defaultData = omit(defaultDataWithoutIgnoringKeys, options?.ignoredKeysForDefaultData ?? []) - const hasDefaultData = areEqualObjectsWithoutOrder( - omit(data, options?.ignoredKeysForDefaultData ?? []), - defaultData - ) - - const redirectUrl = makeCleanUrl(shape, context.url, options?.cleanUrl, data, defaultData) - - return { data, defaultData, hasDefaultData, errors, schema: shape, redirectUrl } -} - -function unwrapSchema( - schema: T, - options: { - default?: boolean - optional?: boolean - nullable?: boolean - array?: boolean - } = {} -) { - if (options.default && schema instanceof z.ZodDefault) { - return unwrapSchema((schema as z.ZodDefault).removeDefault(), options) - } - if (options.optional && schema instanceof z.ZodOptional) { - return unwrapSchema((schema as z.ZodOptional).unwrap(), options) - } - if (options.nullable && schema instanceof z.ZodNullable) { - return unwrapSchema((schema as z.ZodNullable).unwrap(), options) - } - if (options.array && schema instanceof z.ZodArray) { - return unwrapSchema((schema as z.ZodArray).element, options) - } - - return schema -} - -function makeCleanUrl>( - shape: T, - url: URL, - options?: CleanUrlOptions, - data?: Record, - defaultData?: Record -) { - if (!options) return null - - const paramsToRemove = [ - ...(options.removeUneededObjectParams ? getUneededObjectParams(shape, url) : []), - ...(options.removeParams ? getParamsToRemove(shape, url, options.removeParams, data, defaultData) : []), - ] - if (!paramsToRemove.length) return null - - const cleanUrl = new URL(url) - paramsToRemove.forEach(([key, value]) => { - cleanUrl.searchParams.delete(key, value) - }) - return cleanUrl -} - -function getUneededObjectParams>(shape: T, url: URL) { - const objectParamsRegex = Object.entries(shape) - .filter(([_key, paramSchema]) => { - const schema = unwrapSchema(paramSchema, { - default: true, - optional: true, - nullable: true, - }) - return schema instanceof z.ZodObject || schema instanceof z.ZodRecord - }) - .map(([key]) => makeObjectSearchParamKeyRegex(key)) - if (!objectParamsRegex.length) return [] - - const uneededParams = url.searchParams - .entries() - .filter(([key, value]) => objectParamsRegex.some((regex) => regex.test(key)) && value === '') - .toArray() - - return uneededParams -} - -function getParamsToRemove>( - shape: T, - url: URL, - removeParams: NonNullable['removeParams']>, - data?: Record, - defaultData?: Record -) { - return url.searchParams - .entries() - .filter(([key]) => { - const options = key in removeParams ? removeParams[key as K] : undefined - if (!options) return false - - const paramSchema = key in shape ? shape[key as K] : undefined - if (!paramSchema) return false - - switch (options.if) { - case 'another-is-unset': { - return !url.searchParams - .keys() - .some((key2) => key2 === options.prop || makeObjectSearchParamKeyRegex(options.prop).test(key2)) - } - case 'default': { - const dataValue = data && key in data ? data[key as K] : undefined - const defaultDataValue = defaultData && key in defaultData ? defaultData[key as K] : undefined - return isEqual(dataValue, defaultDataValue) - } - default: { - return false - } - } - }) - .toArray() -} diff --git a/web/src/lib/pluralize.ts b/web/src/lib/pluralize.ts deleted file mode 100644 index 43663cf..0000000 --- a/web/src/lib/pluralize.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { transformCase } from './strings' - -const knownPlurals = { - is: { - singular: 'Is', - plural: 'Are', - }, - service: { - singular: 'Service', - plural: 'Services', - }, - user: { - singular: 'User', - plural: 'Users', - }, - note: { - singular: 'Note', - plural: 'Notes', - }, - result: { - singular: 'Result', - plural: 'Results', - }, - request: { - singular: 'Request', - plural: 'Requests', - }, - something: { - singular: 'Something', - plural: 'Somethings', - }, -} as const satisfies Record< - string, - { - singular: string - plural: string - } -> - -type KnownPlural = keyof typeof knownPlurals - -const synonyms = { - are: 'is', -} as const satisfies Record - -type Synonym = keyof typeof synonyms - -export type KnownPluralOrSynonym = KnownPlural | Synonym - -function isKnownPlural(key: string): key is KnownPlural { - return key in knownPlurals -} - -function isKnownPluralSynonym(key: string): key is Synonym { - return key in synonyms -} - -/** - * Formats name into singular or plural form, and case type. - * - * @param entity - Entity name or synonym. - * @param count - Number of entities. - * @param caseType - Case type to apply to the entity name. - */ -export function pluralize( - entity: T, - count: number | null = 1, - caseType: Exclude[1], 'original'> = 'lower' -) { - return pluralizeGeneric(entity, count, caseType) -} - -/** - * Use {@link pluralize} preferably. - * - * Formats name into singular or plural form, and case type. - * If the provided entity is not from the {@link knownPlurals} object, it will return the string with the case type applied. - * - * @param entity - Entity name or synonym. - * @param count - Number of entities. - * @param caseType - Case type to apply to the entity name. - */ -export function pluralizeGeneric( - entity: T, - count: number | null, - caseType: Exclude[1], 'original'> -): string -export function pluralizeGeneric( - entity: T, - count: number | null, - caseType: Exclude[1], 'original'> -): string -export function pluralizeGeneric( - entity: T, - count: number | null = 1, - caseType: Exclude[1], 'original'> = 'lower' -): string { - const originalEntity = isKnownPluralSynonym(entity) ? synonyms[entity] : entity - if (!isKnownPlural(originalEntity)) { - console.warn(`getEntityName: Unknown entity "${originalEntity}"`) - return transformCase(originalEntity, caseType) - } - const { singular, plural } = knownPlurals[originalEntity] - return pluralizeAny(singular, plural, count, caseType) -} - -/** - * Use {@link pluralize} preferably. - * - * Formats name into singular or plural form, and case type. - * - * @param singular - Singular form of the entity. - * @param plural - Plural form of the entity. - * @param count - Number of entities. - * @param caseType - Case type to apply to the entity name. - */ -export function pluralizeAny( - singular: string, - plural: string, - count: number | null = 1, - caseType: Exclude[1], 'original'> = 'lower' -): string { - const name = count === 1 ? singular : plural - return transformCase(name, caseType) -} diff --git a/web/src/lib/prisma.ts b/web/src/lib/prisma.ts deleted file mode 100644 index ace698a..0000000 --- a/web/src/lib/prisma.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { PrismaClient } from '@prisma/client' - -import type { Prisma } from '@prisma/client' - -const findManyAndCount = { - name: 'findManyAndCount', - model: { - $allModels: { - findManyAndCount( - this: Model, - args: Prisma.Exact> - ): Promise< - [Prisma.Result, number, Args extends { take: number } ? number : undefined] - > { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return prisma.$transaction([ - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access - (this as any).findMany(args), - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access - (this as any).count({ where: (args as any).where }), - ]) as any - }, - }, - }, -} - -type FindManyAndCountType = typeof findManyAndCount.model.$allModels.findManyAndCount - -type ModelsWithCustomMethods = { - [Model in keyof PrismaClient]: PrismaClient[Model] extends { - findMany: (...args: any[]) => Promise - } - ? PrismaClient[Model] & { - findManyAndCount: FindManyAndCountType - } - : PrismaClient[Model] -} - -type ExtendedPrismaClient = ModelsWithCustomMethods & PrismaClient - -function prismaClientSingleton(): ExtendedPrismaClient { - const prisma = new PrismaClient().$extends(findManyAndCount) - - return prisma as unknown as ExtendedPrismaClient -} - -declare global { - // eslint-disable-next-line no-var - var prisma: ReturnType | undefined -} - -export const prisma = global.prisma ?? prismaClientSingleton() - -if (process.env.NODE_ENV !== 'production') { - global.prisma = prisma -} diff --git a/web/src/lib/redirectUrls.ts b/web/src/lib/redirectUrls.ts deleted file mode 100644 index 4784947..0000000 --- a/web/src/lib/redirectUrls.ts +++ /dev/null @@ -1,61 +0,0 @@ -export function makeLoginUrl( - currentUrl: URL, - { - redirect, - error, - logout, - message, - }: { - redirect?: URL | string | null - error?: string | null - logout?: boolean - message?: string | null - } = {} -) { - const loginUrl = new URL(currentUrl.origin) - loginUrl.pathname = '/account/login' - - if (error) { - loginUrl.searchParams.set('error', error) - } - - if (logout) { - loginUrl.searchParams.set('logout', 'true') - } - - if (message) { - loginUrl.searchParams.set('message', message) - } - - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const redirectUrl = new URL(redirect || currentUrl) - if (redirectUrl.pathname === '/account/login') { - const redirectUrlRedirectParam = redirectUrl.searchParams.get('redirect') - if (redirectUrlRedirectParam) { - loginUrl.searchParams.set('redirect', redirectUrlRedirectParam) - } - } else { - loginUrl.searchParams.set('redirect', redirectUrl.toString()) - } - - return loginUrl.toString() -} - -export function makeUnimpersonateUrl( - currentUrl: URL, - { - redirect, - }: { - redirect?: URL | string | null - } = {} -) { - const url = new URL(currentUrl.origin) - url.pathname = '/account/impersonate' - url.searchParams.set('stop', 'true') - - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const redirectUrl = new URL(redirect || currentUrl) - url.searchParams.set('redirect', redirectUrl.toString()) - - return url.toString() -} diff --git a/web/src/lib/redis/redisActionsSessions.ts b/web/src/lib/redis/redisActionsSessions.ts deleted file mode 100644 index 97a7547..0000000 --- a/web/src/lib/redis/redisActionsSessions.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { randomUUID } from 'node:crypto' - -import { deserializeActionResult } from 'astro:actions' -import { z } from 'astro:content' -import { REDIS_ACTIONS_SESSION_EXPIRY_SECONDS } from 'astro:env/server' - -import { RedisGenericManager } from './redisGenericManager' - -const dataSchema = z.object({ - actionName: z.string(), - actionResult: z.union([ - z.object({ - type: z.literal('data'), - contentType: z.literal('application/json+devalue'), - status: z.literal(200), - body: z.string(), - }), - z.object({ - type: z.literal('error'), - contentType: z.literal('application/json'), - status: z.number(), - body: z.string(), - }), - z.object({ - type: z.literal('empty'), - status: z.literal(204), - }), - ]), -}) - -class RedisActionsSessions extends RedisGenericManager { - async store(data: z.input) { - const sessionId = randomUUID() - - const parsedData = dataSchema.parse(data) - await this.redisClient.set(`actions-session:${sessionId}`, JSON.stringify(parsedData), { - EX: this.expirationTime, - }) - - return sessionId - } - - async get(sessionId: string | null | undefined) { - if (!sessionId) return null - - const key = `actions-session:${sessionId}` - - const rawData = await this.redisClient.get(key) - if (!rawData) return null - - const data = dataSchema.parse(JSON.parse(rawData)) - const deserializedActionResult = deserializeActionResult(data.actionResult) - - return { - deserializedActionResult, - ...data, - } - } - - async delete(sessionId: string | null | undefined) { - if (!sessionId) return - - await this.redisClient.del(`actions-session:${sessionId}`) - } -} - -export const redisActionsSessions = await RedisActionsSessions.createAndConnect({ - expirationTime: REDIS_ACTIONS_SESSION_EXPIRY_SECONDS, -}) diff --git a/web/src/lib/redis/redisGenericManager.ts b/web/src/lib/redis/redisGenericManager.ts deleted file mode 100644 index 40afd8f..0000000 --- a/web/src/lib/redis/redisGenericManager.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { REDIS_URL } from 'astro:env/server' -import { createClient } from 'redis' - -type RedisGenericManagerOptions = { - expirationTime: number -} - -export abstract class RedisGenericManager { - protected redisClient - /** The expiration time of the Redis session. In seconds. */ - readonly expirationTime: number - - /** @deprecated Use {@link createAndConnect} instead */ - constructor(options: RedisGenericManagerOptions) { - this.redisClient = createClient({ - url: REDIS_URL, - }) - - this.expirationTime = options.expirationTime - - this.redisClient.on('error', (err) => { - console.error(`[${this.constructor.name}] `, err) - }) - } - - /** Closes the Redis connection */ - async close(): Promise { - await this.redisClient.quit() - } - - /** Connects to the Redis connection */ - async connect(): Promise { - await this.redisClient.connect() - } - - static async createAndConnect( - this: new (options: RedisGenericManagerOptions) => T, - options: RedisGenericManagerOptions - ): Promise { - const instance = new this(options) - - await instance.connect() - return instance - } -} diff --git a/web/src/lib/redis/redisImpersonationSessions.ts b/web/src/lib/redis/redisImpersonationSessions.ts deleted file mode 100644 index 5772966..0000000 --- a/web/src/lib/redis/redisImpersonationSessions.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { randomUUID } from 'node:crypto' - -import { z } from 'astro:content' -import { REDIS_IMPERSONATION_SESSION_EXPIRY_SECONDS } from 'astro:env/server' - -import { RedisGenericManager } from './redisGenericManager' - -const dataSchema = z.object({ - adminId: z.number(), - targetId: z.number(), -}) - -class RedisImpersonationSessions extends RedisGenericManager { - async store(data: z.input) { - const sessionId = randomUUID() - - const parsedData = dataSchema.parse(data) - await this.redisClient.set(`impersonation-session:${sessionId}`, JSON.stringify(parsedData), { - EX: this.expirationTime, - }) - - return sessionId - } - - async get(sessionId: string | null | undefined) { - if (!sessionId) return null - - const key = `impersonation-session:${sessionId}` - - const rawData = await this.redisClient.get(key) - if (!rawData) return null - - return dataSchema.parse(JSON.parse(rawData)) - } - - async delete(sessionId: string | null | undefined) { - if (!sessionId) return - - await this.redisClient.del(`impersonation-session:${sessionId}`) - } -} - -export const redisImpersonationSessions = await RedisImpersonationSessions.createAndConnect({ - expirationTime: REDIS_IMPERSONATION_SESSION_EXPIRY_SECONDS, -}) diff --git a/web/src/lib/redis/redisPreGeneratedSecretTokens.ts b/web/src/lib/redis/redisPreGeneratedSecretTokens.ts deleted file mode 100644 index d9a49cc..0000000 --- a/web/src/lib/redis/redisPreGeneratedSecretTokens.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { REDIS_PREGENERATED_TOKEN_EXPIRY_SECONDS } from 'astro:env/server' - -import { RedisGenericManager } from './redisGenericManager' - -class RedisPreGeneratedSecretTokens extends RedisGenericManager { - /** - * Stores a pre-generated token with expiration - * @param token The pre-generated token - */ - async storePreGeneratedToken(token: string): Promise { - await this.redisClient.set(`pregenerated-user-secret-token:${token}`, '1', { - EX: this.expirationTime, - }) - } - - /** - * Validates and consumes a pre-generated token - * @param token The token to validate - * @returns true if token was valid and consumed, false otherwise - */ - async validateAndConsumePreGeneratedToken(token: string): Promise { - const key = `pregenerated-user-secret-token:${token}` - const exists = await this.redisClient.exists(key) - if (exists) { - await this.redisClient.del(key) - return true - } - return false - } -} - -export const redisPreGeneratedSecretTokens = await RedisPreGeneratedSecretTokens.createAndConnect({ - expirationTime: REDIS_PREGENERATED_TOKEN_EXPIRY_SECONDS, -}) diff --git a/web/src/lib/redis/redisSessions.ts b/web/src/lib/redis/redisSessions.ts deleted file mode 100644 index 80a0102..0000000 --- a/web/src/lib/redis/redisSessions.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { randomBytes } from 'crypto' - -import { REDIS_USER_SESSION_EXPIRY_SECONDS } from 'astro:env/server' - -import { RedisGenericManager } from './redisGenericManager' - -class RedisSessions extends RedisGenericManager { - /** - * Generates a random session ID - */ - private generateSessionId(): string { - return randomBytes(32).toString('hex') - } - - /** - * Creates a new session for a user - * @param userSecretTokenHash The ID of the user - * @returns The generated session ID - */ - async createSession(userSecretTokenHash: string): Promise { - const sessionId = this.generateSessionId() - // Store the session with user ID - await this.redisClient.set(`session:${sessionId}`, userSecretTokenHash, { - EX: this.expirationTime, - }) - - // Store session ID in user's sessions set - await this.redisClient.sAdd(`user:${userSecretTokenHash}:sessions`, sessionId) - - return sessionId - } - - /** - * Gets the user ID associated with a session - * @param sessionId The session ID to look up - * @returns The user ID or null if session not found - */ - async getUserBySessionId(sessionId: string): Promise { - const userSecretTokenHash = await this.redisClient.get(`session:${sessionId}`) - return userSecretTokenHash - } - - /** - * Deletes all sessions for a user - * @param userSecretTokenHash The ID of the user whose sessions should be deleted - */ - async deleteUserSessions(userSecretTokenHash: string): Promise { - // Get all session IDs for the user - const sessionIds = await this.redisClient.sMembers(`user:${userSecretTokenHash}:sessions`) - - if (sessionIds.length > 0) { - // Delete each session - // Delete sessions one by one to avoid type issues with spread operator - for (const sessionId of sessionIds) { - await this.redisClient.del(`session:${sessionId}`) - } - - // Delete the set of user's sessions - await this.redisClient.del(`user:${userSecretTokenHash}:sessions`) - } - } - - /** - * Deletes a specific session - * @param sessionId The session ID to delete - */ - async deleteSession(sessionId: string): Promise { - const userSecretTokenHash = await this.getUserBySessionId(sessionId) - if (userSecretTokenHash) { - await this.redisClient.del(`session:${sessionId}`) - await this.redisClient.sRem(`user:${userSecretTokenHash}:sessions`, sessionId) - } - } -} - -export const redisSessions = await RedisSessions.createAndConnect({ - expirationTime: REDIS_USER_SESSION_EXPIRY_SECONDS, -}) diff --git a/web/src/lib/schema.ts b/web/src/lib/schema.ts deleted file mode 100644 index 44a427a..0000000 --- a/web/src/lib/schema.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { SITE_URL } from 'astro:env/client' - -import type { Organization } from 'schema-dts' - -export const KYCNOTME_SCHEMA_MINI = { - '@type': 'Organization', - name: 'KYCnot.me', - sameAs: SITE_URL, - url: SITE_URL, -} as const satisfies Organization diff --git a/web/src/lib/sortSeed.ts b/web/src/lib/sortSeed.ts deleted file mode 100644 index 03873ff..0000000 --- a/web/src/lib/sortSeed.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { SEARCH_PARAM_CHARACTERS_NO_ESCAPE } from '../constants/characters' -import { getRandom } from '../lib/arrays' - -export const makeSortSeed = () => { - const firstChar = getRandom(SEARCH_PARAM_CHARACTERS_NO_ESCAPE) - const secondChar = getRandom([...SEARCH_PARAM_CHARACTERS_NO_ESCAPE, ''] as const) - return `${firstChar}${secondChar}` -} diff --git a/web/src/lib/strings.ts b/web/src/lib/strings.ts deleted file mode 100644 index ca6dfcd..0000000 --- a/web/src/lib/strings.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/** - * Normalize a string by removing accents and converting it to lowercase. - * - * @example - * normalize(' Café') // 'cafe' - */ -const normalize = (str: string): string => { - return str - .normalize('NFD') - .replace(/[\u0300-\u036f]/g, '') - .toLowerCase() - .trim() -} - -/** - * Compare two strings after normalizing them. - */ -export const areSameNormalized = (str1: string, str2: string): boolean => { - return normalize(str1) === normalize(str2) -} - -export type TransformCaseType = 'lower' | 'original' | 'sentence' | 'title' | 'upper' - -/** - * Transform a string to a different case. - * - * @example - * transformCase('hello WORLD', 'lower') // 'hello world' - * transformCase('hello WORLD', 'upper') // 'HELLO WORLD' - * transformCase('hello WORLD', 'sentence') // 'Hello world' - * transformCase('hello WORLD', 'title') // 'Hello World' - * transformCase('hello WORLD', 'original') // 'hello WORLD' - */ -export const transformCase = ( - str: T, - caseType: C -): C extends 'lower' - ? Lowercase - : C extends 'upper' - ? Uppercase - : C extends 'sentence' - ? Capitalize> - : C extends 'title' - ? Capitalize> - : T => { - switch (caseType) { - case 'lower': - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return str.toLowerCase() as any - case 'upper': - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return str.toUpperCase() as any - case 'sentence': - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return (str.charAt(0).toUpperCase() + str.slice(1).toLowerCase()) as any - case 'title': - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return str - .split(' ') - .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) - .join(' ') as any - case 'original': - default: - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return str as any - } -} diff --git a/web/src/lib/timeAgo.ts b/web/src/lib/timeAgo.ts deleted file mode 100644 index 7c5b189..0000000 --- a/web/src/lib/timeAgo.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { addDays, format, isBefore, isToday, isYesterday } from 'date-fns' -import TimeAgo from 'javascript-time-ago' -import en from 'javascript-time-ago/locale/en' - -import { transformCase, type TransformCaseType } from './strings' - -TimeAgo.addDefaultLocale(en) - -export const timeAgo = new TimeAgo('en-US') - -export type FormatDateShortOptions = { - prefix?: boolean - hourPrecision?: boolean - daysUntilDate?: number | null - caseType?: TransformCaseType - hoursShort?: boolean -} - -export function formatDateShort( - date: Date, - { - prefix = true, - hourPrecision = true, - daysUntilDate = null, - caseType, - hoursShort = false, - }: FormatDateShortOptions = {} -) { - const text = (() => { - if (isToday(date)) { - if (hourPrecision) return timeAgo.format(date, hoursShort ? 'twitter-minute-now' : 'round-minute') - return 'today' - } - if (isYesterday(date)) return 'yesterday' - - if (daysUntilDate && isBefore(date, addDays(new Date(), daysUntilDate))) { - return timeAgo.format(date, 'round-minute') - } - - const currentYear = new Date().getFullYear() - const dateYear = date.getFullYear() - const formattedDate = dateYear === currentYear ? format(date, 'MMM d') : format(date, 'MMM d, yyyy') - return prefix ? `on ${formattedDate}` : formattedDate - })() - - if (!caseType) return text - - return transformCase(text, caseType) -} diff --git a/web/src/lib/timeTrapSecret.ts b/web/src/lib/timeTrapSecret.ts deleted file mode 100644 index 5319146..0000000 --- a/web/src/lib/timeTrapSecret.ts +++ /dev/null @@ -1,6 +0,0 @@ -import crypto from 'crypto' - -// Generate a 32-byte secret key once when the module is first loaded -const timeTrapSecretKey = crypto.randomBytes(32) - -export { timeTrapSecretKey } diff --git a/web/src/lib/urls.ts b/web/src/lib/urls.ts deleted file mode 100644 index a35b3c6..0000000 --- a/web/src/lib/urls.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { escapeRegExp } from 'lodash-es' - -export const createPageUrl = ( - page: number, - currentUrl: URL | string, - otherParams?: Record | URLSearchParams -) => { - const url = new URL(currentUrl) - if (otherParams) { - if (otherParams instanceof URLSearchParams) { - otherParams.forEach((value, key) => { - url.searchParams.set(key, value) - }) - } else { - Object.entries(otherParams).forEach(([key, value]) => { - if (value !== undefined && value !== null) { - url.searchParams.set(key, value) - } - }) - } - } - url.searchParams.set('page', page.toString()) - - return url.toString() -} - -export function urlParamsToFormData(params: URLSearchParams) { - const formData = new FormData() - params.forEach((value, key) => { - formData.append(key, value) - }) - return formData -} - -export function urlParamsToObject(params: URLSearchParams) { - return Object.fromEntries(params.entries()) -} - -export function urlWithParams( - url: URL | string, - params: Record, - { clearExisting }: { clearExisting?: boolean } = { clearExisting: false } -) { - const urlObj = new URL(url) - if (clearExisting) { - const keysToDelete = Array.from(urlObj.searchParams.keys()) - keysToDelete.forEach((key) => { - urlObj.searchParams.delete(key) - }) - } - Object.entries(params).forEach(([key, value]) => { - if (Array.isArray(value)) { - value.forEach((v) => { - urlObj.searchParams.append(key, String(v)) - }) - } else if (value === null || value === undefined) { - urlObj.searchParams.delete(key) - } else { - urlObj.searchParams.set(key, String(value)) - } - }) - return urlObj.toString() -} - -export function makeObjectSearchParamKeyRegex(key: string) { - return new RegExp(`^${escapeRegExp(key)}-(.*)$`) -} - -/** - * Parses the value of an object from a URL with zod. Assuming this format: `key[subkey]=value` - * - * Returns an object with the keys as the subkeys and the values as the values. - * Or `undefined` if there are no subkeys. - * - * If there is no subkey (`key=value`), the subkey is set to an empty string. - * - * @example - * ```ts - * const searchParams = new URLSearchParams('tag-en=include&tag-fr=exclude&tag-es=') - * const value = getObjectSearchParam(searchParams, 'tag') - * // value: { en: 'include', fr: 'exclude'} - * ``` - */ -export function getObjectSearchParam( - params: URLSearchParams, - key: string, - { - ignoreEmptyValues = true, - emptyObjectBecomesUndefined = true, - }: { - ignoreEmptyValues?: boolean - emptyObjectBecomesUndefined?: boolean - } = {} -) { - const keyPattern = makeObjectSearchParamKeyRegex(key) - - const entries = Array.from(params.entries()).flatMap(([paramKey, paramValue]) => { - if (ignoreEmptyValues && paramValue === '') return [] - if (paramKey === key) return [['', paramValue]] as const - - const subKey = paramKey.match(keyPattern)?.[1] - if (subKey === undefined) return [] - return [[subKey, paramValue]] as const - }) - - if (entries.length === 0) return emptyObjectBecomesUndefined ? undefined : {} - return Object.fromEntries(entries) -} - -export function urlDomain(url: URL | string) { - if (typeof url === 'string') { - return url.replace(/^(https?:\/\/)?(www\.)?/, '').replace(/\/(index\.html)?$/, '') - } - return url.origin -} diff --git a/web/src/lib/userCookies.ts b/web/src/lib/userCookies.ts deleted file mode 100644 index 4cab300..0000000 --- a/web/src/lib/userCookies.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { stopImpersonating } from './impersonation' -import { prisma } from './prisma' -import { redisSessions } from './redis/redisSessions' - -import type { APIContext, AstroCookies, AstroCookieSetOptions } from 'astro' - -const COOKIE_NAME = 'user_session_id' -const COOKIE_MAX_AGE = 60 * 60 * 24 * 7 // 1 week - -const defaultCookieOptions = { - path: '/', - secure: true, - httpOnly: true, - sameSite: 'strict', - maxAge: COOKIE_MAX_AGE, -} as const satisfies AstroCookieSetOptions - -export function getUserSessionIdCookie(cookies: AstroCookies) { - return cookies.get(COOKIE_NAME)?.value -} - -export async function getUserFromCookies(cookies: AstroCookies) { - const userSessionId = getUserSessionIdCookie(cookies) - if (!userSessionId) return null - - const userSecretTokenHash = await redisSessions.getUserBySessionId(userSessionId) - if (!userSecretTokenHash) return null - - return prisma.user.findFirst({ - where: { - secretTokenHash: userSecretTokenHash, - }, - }) -} - -export async function setUserSessionIdCookie( - cookies: AstroCookies, - userSecretTokenHash: string, - options: AstroCookieSetOptions = {} -) { - const sessId = await redisSessions.createSession(userSecretTokenHash) - cookies.set(COOKIE_NAME, sessId, { - ...defaultCookieOptions, - ...options, - }) -} - -export async function removeUserSessionIdCookie(cookies: AstroCookies) { - const sessionId = cookies.get(COOKIE_NAME)?.value - if (sessionId) { - await redisSessions.deleteSession(sessionId) - } - cookies.delete(COOKIE_NAME, { path: '/' }) -} - -export async function logout(context: Pick) { - await stopImpersonating(context) - - await removeUserSessionIdCookie(context.cookies) - - context.locals.user = null - context.locals.actualUser = null -} - -export async function login( - context: Pick, - user: NonNullable -) { - await stopImpersonating(context) - - await setUserSessionIdCookie(context.cookies, user.secretTokenHash) - - await prisma.user.update({ - where: { id: user.id }, - data: { lastLoginAt: new Date() }, - }) - - context.locals.user = user - context.locals.actualUser = null -} diff --git a/web/src/lib/userSecretToken.ts b/web/src/lib/userSecretToken.ts deleted file mode 100644 index 63a63ee..0000000 --- a/web/src/lib/userSecretToken.ts +++ /dev/null @@ -1,149 +0,0 @@ -import crypto from 'crypto' - -import { z } from 'astro/zod' -import { escapeRegExp } from 'lodash-es' - -import { - DIGIT_CHARACTERS, - LOWERCASE_CONSONANT_CHARACTERS, - LOWERCASE_VOWEL_CHARACTERS, -} from '../constants/characters' - -import { getRandom, typedJoin } from './arrays' -import { DEPLOYMENT_MODE } from './envVariables' -import { transformCase } from './strings' - -const DIGEST = 'sha512' - -const USER_SECRET_TOKEN_LETTERS_SEGMENT_REGEX = - `(?:(?:[${typedJoin(LOWERCASE_VOWEL_CHARACTERS)}${transformCase(typedJoin(LOWERCASE_VOWEL_CHARACTERS), 'upper')}][${typedJoin(LOWERCASE_CONSONANT_CHARACTERS)}${transformCase(typedJoin(LOWERCASE_CONSONANT_CHARACTERS), 'upper')}]){2})` as const -const USER_SECRET_TOKEN_DIGITS_SEGMENT_REGEX = `(?:[${typedJoin(DIGIT_CHARACTERS)}]{4})` as const -const USER_SECRET_TOKEN_SEPARATOR_REGEX = '(?:(-| )+)' - -export const includeDevUsers = DEPLOYMENT_MODE !== 'production' - -const USER_SECRET_TOKEN_DEV_USERS_REGEX = (() => { - const specialUsersData = [ - { - envToken: 'DEV_ADMIN_USER_SECRET_TOKEN', - defaultToken: 'admin', - }, - { - envToken: 'DEV_VERIFIER_USER_SECRET_TOKEN', - defaultToken: 'verifier', - }, - { - envToken: 'DEV_VERIFIED_USER_SECRET_TOKEN', - defaultToken: 'verified', - }, - { - envToken: 'DEV_NORMAL_USER_SECRET_TOKEN', - defaultToken: 'normal', - }, - { - envToken: 'DEV_SPAM_USER_SECRET_TOKEN', - defaultToken: 'spam', - }, - ] as const satisfies { - envToken: string - defaultToken: string - }[] - - const env = - // This file can also be called from faker.ts, where import.meta.env is not available - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - (import.meta.env - ? Object.fromEntries(specialUsersData.map(({ envToken }) => [envToken, import.meta.env[envToken]])) - : undefined) ?? process.env - - return `(?:${typedJoin( - specialUsersData.map(({ envToken, defaultToken }) => - typedJoin( - ((env[envToken] as string | undefined) ?? defaultToken).match(/(.{4}|.{1,3}$)/g)?.map( - (segment) => - `${segment - .split('') - .map((char) => `(?:${escapeRegExp(char.toUpperCase())}|${escapeRegExp(char.toLowerCase())})`) - .join('')}${USER_SECRET_TOKEN_SEPARATOR_REGEX}?` - ) ?? [] - ) - ), - '|' - )})` as const -})() - -const USER_SECRET_TOKEN_FULL_REGEX_STRING = - `(?:(?:${USER_SECRET_TOKEN_LETTERS_SEGMENT_REGEX}${USER_SECRET_TOKEN_SEPARATOR_REGEX}?){4}${USER_SECRET_TOKEN_DIGITS_SEGMENT_REGEX})` as const -export const USER_SECRET_TOKEN_REGEX_STRING = - `^(?:${USER_SECRET_TOKEN_FULL_REGEX_STRING}${includeDevUsers ? `|${USER_SECRET_TOKEN_DEV_USERS_REGEX}` : ''})$` as const -export const USER_SECRET_TOKEN_REGEX = new RegExp(USER_SECRET_TOKEN_REGEX_STRING) - -export const userSecretTokenZodSchema = z - .string() - .regex(USER_SECRET_TOKEN_REGEX) - .transform(parseUserSecretToken) - -export function generateUserSecretToken(): string { - const token = [ - getRandom(LOWERCASE_VOWEL_CHARACTERS), - getRandom(LOWERCASE_CONSONANT_CHARACTERS), - getRandom(LOWERCASE_VOWEL_CHARACTERS), - getRandom(LOWERCASE_CONSONANT_CHARACTERS), - - getRandom(LOWERCASE_VOWEL_CHARACTERS), - getRandom(LOWERCASE_CONSONANT_CHARACTERS), - getRandom(LOWERCASE_VOWEL_CHARACTERS), - getRandom(LOWERCASE_CONSONANT_CHARACTERS), - - getRandom(LOWERCASE_VOWEL_CHARACTERS), - getRandom(LOWERCASE_CONSONANT_CHARACTERS), - getRandom(LOWERCASE_VOWEL_CHARACTERS), - getRandom(LOWERCASE_CONSONANT_CHARACTERS), - - getRandom(LOWERCASE_VOWEL_CHARACTERS), - getRandom(LOWERCASE_CONSONANT_CHARACTERS), - getRandom(LOWERCASE_VOWEL_CHARACTERS), - getRandom(LOWERCASE_CONSONANT_CHARACTERS), - - getRandom(DIGIT_CHARACTERS), - getRandom(DIGIT_CHARACTERS), - getRandom(DIGIT_CHARACTERS), - getRandom(DIGIT_CHARACTERS), - ].join('') - - return parseUserSecretToken(token) -} - -export function hashUserSecretToken(token: string): string { - return crypto.createHash(DIGEST).update(token).digest('hex') -} - -export function parseUserSecretToken(token: string): string { - if (!USER_SECRET_TOKEN_REGEX.test(token)) { - throw new Error( - `Invalid user secret token. Token "${token}" does not match regex ${USER_SECRET_TOKEN_REGEX_STRING}` - ) - } - - return token.toLocaleLowerCase().replace(new RegExp(USER_SECRET_TOKEN_SEPARATOR_REGEX, 'g'), '') -} - -export function prettifyUserSecretToken(token: string): string { - const parsedToken = parseUserSecretToken(token) - - const groups = parsedToken.toLocaleUpperCase().match(/.{4}/g) - if (!groups || groups.length !== 5) { - throw new Error('Error while prettifying user secret token') - } - return groups.join('-') -} - -/** - * Verify a token against a stored hash using a constant-time comparison - */ -export function verifyUserSecretToken(token: string, hash: string): boolean { - const correctHash = hashUserSecretToken(token) - - // Use crypto.timingSafeEqual to prevent timing attacks - return crypto.timingSafeEqual(Buffer.from(correctHash, 'hex'), Buffer.from(hash, 'hex')) -} diff --git a/web/src/lib/zodUtils.ts b/web/src/lib/zodUtils.ts deleted file mode 100644 index aeda57b..0000000 --- a/web/src/lib/zodUtils.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { z, type ZodTypeAny } from 'astro/zod' -import { round } from 'lodash-es' - -const addZodPipe = (schema: ZodTypeAny, zodPipe?: ZodTypeAny) => { - return zodPipe ? schema.pipe(zodPipe) : schema -} - -/** - * The difference between this and `z.coerce.number()` is that an empty string won't be coerced to 0. - * - * If you don't accept 0, just use `z.coerce.number().int().positive()` instead. - */ -export const zodCohercedNumber = (zodPipe?: ZodTypeAny) => - addZodPipe(z.number().or(z.string().nonempty()), zodPipe) - -export const zodUrlOptionalProtocol = z.preprocess( - (input) => { - if (typeof input !== 'string') return input - const trimmedVal = input.trim() - return !/^\w+:\/\//i.test(trimmedVal) ? `https://${trimmedVal}` : trimmedVal - }, - z.string().refine((value) => /^(https?):\/\/(?=.*\.[a-z]{2,})[^\s$.?#].[^\s]*$/i.test(value), { - message: 'Invalid URL', - }) -) - -const stringToArrayFactory = (delimiter: RegExp | string = ',') => { - return (input: T) => - typeof input !== 'string' - ? (input ?? undefined) - : input - .split(delimiter) - .map((item) => item.trim()) - .filter((item) => item !== '') -} - -export const stringListOfUrlsSchema = z.preprocess( - stringToArrayFactory(/[\s,\n]+/), - z.array(zodUrlOptionalProtocol).default([]) -) - -export const stringListOfUrlsSchemaRequired = z.preprocess( - stringToArrayFactory(/[\s,\n]+/), - z.array(zodUrlOptionalProtocol).min(1) -) - -export const MAX_IMAGE_SIZE = 5 * 1024 * 1024 // 5MB - -export const ACCEPTED_IMAGE_TYPES = [ - 'image/svg+xml', - 'image/png', - 'image/jpeg', - 'image/jxl', - 'image/avif', - 'image/webp', -] as const satisfies string[] - -export const imageFileSchema = z - .instanceof(File) - .optional() - .nullable() - .transform((file) => (!file || file.size === 0 || !file.name ? undefined : file)) - .refine( - (file) => !file || file.size <= MAX_IMAGE_SIZE, - `Max image size is ${round(MAX_IMAGE_SIZE / 1024 / 1024, 3).toLocaleString()}MB.` - ) - .refine( - (file) => !file || ACCEPTED_IMAGE_TYPES.some((type) => file.type === type), - 'Only SVG, PNG, JPG, JPEG XL, AVIF, WebP formats are supported.' - ) - -export const imageFileSchemaRequired = imageFileSchema.refine((file) => !!file, 'Required') diff --git a/web/src/middleware.ts b/web/src/middleware.ts deleted file mode 100644 index f76f906..0000000 --- a/web/src/middleware.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { getActionContext } from 'astro:actions' -import { defineMiddleware, sequence } from 'astro:middleware' - -import { ErrorBanners, getMessagesFromUrl } from './lib/errorBanners' -import { getImpersonationInfo } from './lib/impersonation' -import { makeUserWithKarmaUnlocks } from './lib/karmaUnlocks' -import { prisma } from './lib/prisma' -import { makeLoginUrl } from './lib/redirectUrls' -import { redisActionsSessions } from './lib/redis/redisActionsSessions' -import { getUserFromCookies } from './lib/userCookies' - -const ACTION_SESSION_COOKIE = 'action-session-id' - -const preventFormResubmitAndStoreActionErrors = defineMiddleware(async (context, next) => { - if (context.isPrerendered) return next() - - const { action, setActionResult, serializeActionResult } = getActionContext(context) - - const sessionId = context.cookies.get(ACTION_SESSION_COOKIE)?.value - const session = await redisActionsSessions.get(sessionId) - - if (session) { - setActionResult(session.actionName, session.actionResult) - - if (session.deserializedActionResult.error) { - context.locals.banners.add({ - uiMessage: session.deserializedActionResult.error.message, - type: 'error', - origin: 'action', - error: session.deserializedActionResult.error, - }) - } - - await redisActionsSessions.delete(sessionId) - context.cookies.delete(ACTION_SESSION_COOKIE) - return next() - } - - if (action) { - const actionResult = await action.handler() - - if (actionResult.error) { - context.locals.banners.add({ - uiMessage: actionResult.error.message, - type: 'error', - origin: 'action', - error: actionResult.error, - }) - } - - if (action.calledFrom === 'form') { - const sessionId = await redisActionsSessions.store({ - actionName: action.name, - actionResult: serializeActionResult(actionResult), - }) - - context.cookies.set(ACTION_SESSION_COOKIE, sessionId, { - path: '/', - httpOnly: true, - secure: true, - sameSite: 'strict', - maxAge: redisActionsSessions.expirationTime, - }) - - if (actionResult.error) { - const referer = context.request.headers.get('Referer') - if (!referer) { - throw new Error('Internal: Referer unexpectedly missing from Action POST request.') - } - return context.redirect(referer) - } - return context.redirect(context.originPathname) - } - } - - return next() -}) - -const authenticate = defineMiddleware(async (context, next) => { - const user = await getUserFromCookies(context.cookies) - context.locals.user = makeUserWithKarmaUnlocks(user) - - return next() -}) - -const impersonate = defineMiddleware(async (context, next) => { - context.locals.actualUser = null - - const user = context.locals.user - if (user?.admin) { - const impersonationInfo = await getImpersonationInfo(context.cookies) - - if (impersonationInfo && impersonationInfo.adminId === user.id) { - const impersonatedUser = await prisma.user.findUnique({ - where: { id: impersonationInfo.targetId }, - }) - - if (impersonatedUser) { - context.locals.actualUser = user - context.locals.user = makeUserWithKarmaUnlocks(impersonatedUser) - } - } - } - - return next() -}) - -const protectRoutes = defineMiddleware(async (context, next) => { - const user = context.locals.user - - if (context.url.pathname.startsWith('/admin')) { - if (!user) { - return Response.redirect(makeLoginUrl(context.url, { message: 'Login as admin to access this page' })) - } - - if (!user.admin) { - const accessDeniedUrl = new URL(context.url.origin) - accessDeniedUrl.pathname = '/access-denied' - accessDeniedUrl.searchParams.set('reasonType', 'admin-required') - accessDeniedUrl.searchParams.set('redirect', context.url.toString()) - return Response.redirect(accessDeniedUrl.toString()) - } - } - - return next() -}) - -const makeIds = defineMiddleware(async (context, next) => { - const prefixCount = new Map() - - context.locals.makeId = (prefix: T) => { - const count = (prefixCount.get(prefix) ?? 0) + 1 - prefixCount.set(prefix, count) - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - return `${prefix}-${count}-${crypto.randomUUID()}` as const - } - - return next() -}) - -const errors = defineMiddleware(async (context, next) => { - const messagesFromUrl = getMessagesFromUrl(context) - context.locals.banners = new ErrorBanners(messagesFromUrl) - - return next() -}) - -export const onRequest = sequence( - errors, - authenticate, - impersonate, - protectRoutes, - preventFormResubmitAndStoreActionErrors, - makeIds -) diff --git a/web/src/pages/404.astro b/web/src/pages/404.astro deleted file mode 100644 index 8d60c43..0000000 --- a/web/src/pages/404.astro +++ /dev/null @@ -1,101 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' - -import BaseLayout from '../layouts/BaseLayout.astro' ---- - - -

- 404 -

-
-
-
-
-
- -
-

Page not found

-

The page doesn't exist, double check the URL.

- - - Home - -
-
- - diff --git a/web/src/pages/500.astro b/web/src/pages/500.astro deleted file mode 100644 index 59c587e..0000000 --- a/web/src/pages/500.astro +++ /dev/null @@ -1,105 +0,0 @@ ---- -import { z } from 'astro/zod' -import { Icon } from 'astro-icon/components' - -import { SUPPORT_EMAIL } from '../constants/project' -import BaseLayout from '../layouts/BaseLayout.astro' -import { DEPLOYMENT_MODE } from '../lib/envVariables' -import { zodParseQueryParamsStoringErrors } from '../lib/parseUrlFilters' - -type Props = { - error: unknown -} - -const { error } = Astro.props - -const { - data: { message }, -} = zodParseQueryParamsStoringErrors({ message: z.string().optional() }, Astro) ---- - - - -

500

-

- Server Error -

-

- {/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing */} - {message || 'Sorry, something crashed on the server.'} -

- { - (DEPLOYMENT_MODE !== 'production' || Astro.locals.user?.admin) && ( -
- {error instanceof Error - ? error.message - : error === undefined - ? // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - message || 'undefined' - : typeof error === 'object' - ? JSON.stringify(error, null, 2) - : String(error as unknown)} -
- ) - } - - -
- - diff --git a/web/src/pages/about.md b/web/src/pages/about.md deleted file mode 100644 index 9e52372..0000000 --- a/web/src/pages/about.md +++ /dev/null @@ -1,211 +0,0 @@ ---- -layout: ../layouts/MarkdownLayout.astro -title: About -author: KYCnot.me -pubDate: 2025-05-15 -description: 'Learn how KYCnot.me website works and about our mission to protect privacy in cryptocurrency.' ---- - -## What is this page? - -KYCnot.me is a directory of trustworthy alternatives for buying, exchanging, trading, and using cryptocurrencies without having to disclose your identity, thus preserving your right to privacy. - -## What is KYC? - -**KYC** stands for _Know Your Customer_, a process designed to protect financial institutions against fraud, corruption, money laundering and terrorist financing. - -The truth is that KYC is a **direct attack on our privacy** and puts us in disadvantage against the governments. **True criminals don't care about KYC policies**. True criminals know perfectly how to avoid such policies. In fact, they normally use the FIAT system and don't even need to use cryptocurrencies. Banks are the biggest money launders, the [HSBC scandal](https://www.reuters.com/business/hsbc-fined-85-mln-anti-money-laundering-failings-2021-12-17/), [Nordea](https://www.reuters.com/article/us-nordea-bnk-moneylaundering-idUSKCN1QL11S) or [Swedbank](https://www.reuters.com/article/us-europe-moneylaundering-swedbank/swedbank-hit-with-record-386-million-fine-over-baltic-money-laundering-breaches-idUSKBN2163LU) are just some examples. - -Chainalysis found that only 0.34% of the transaction volume with cryptocurrencies in 2023 was attributable to criminal activity. Bitcoin's share of this is significantly lower. Illicit transactions with Euros accounted for 1% of the EU's GDP or €110 billion in 2010. [[1]](https://www.chainalysis.com/blog/2024-crypto-crime-report-introduction/) [[2]](https://transparency.eu/priority/financial-flows-crime/) - -KYC only affects small individuals like you and me. It is an annoying procedure that **forces us to trust our personal information to a third party** in order to buy, use or unlock our funds. We should start using cryptocurrencies as they were intended to be used: without barriers. - -## Why does this site exist? - -Crypto was born to free us from banks and governments controlling our money. Simple as that. - -When exchanges require your ID and personal information through KYC, they undermine the core principle of cryptocurrency: privacy. Not everyone possesses an ID, and not everyone resides in a "supported" country. Small businesses often struggle with the burden of compliance and the fear of hefty fines. Moreover, exchanges are [targets for hackers](https://www.reuters.com/business/coinbase-says-cyber-criminals-stole-account-data-some-customers-2025-05-15/?ref=guptadeepak.com), putting your sensitive data at risk of theft. - -KYC turns crypto back into the system we're trying to escape. That's why I built this site - to help you use crypto the way it was meant to be used: privately. - -## Why only Bitcoin and Monero? - -**Bitcoin**: It's the initial spark of the decentralized money. A solid project with a strong community. It is the most well-known and widespread cryptocurrency. - -**Monero**: If you're looking for digital cash that's truly private, Monero is it. It's designed for privacy, works like real cash (fungible), has low fees, and is supported by a dedicated, long-standing community. - -While the main focus is on Bitcoin and Monero, you'll find that many of the listed services also accept other cryptocurrencies, such as Ethereum or Litecoin. - -## User Accounts - -You can [create an account](/account/generate) to suggest new services or share your feedback on service pages. - -User accounts do not require any personal information. Your username will be **randomly** generated to prevent impersonation and protect your privacy. - -When you create an account, you are given a **login key**. Login keys are **displayed only once**. Be sure to **store it securely**, as it **cannot be recovered** if lost. It is recommended to use a password manager like [Bitwarden](https://bitwarden.com) or [KeePassXC](https://keepassxc.org/). - -### User Karma - -Users earn karma by participating in the community. When your comments get approved, or when making contributions. As your karma grows, you'll unlock **special features**, which are detailed on your [user profile page](/account). - -### Verified and Affiliated Users - -Some users are **verified**, this means that the moderators have confirmed that the user is related to a specific link or service. The verification is directly linked to the URL, ensuring that the person behind the username has a legitimate connection to the service they claim to represent. This verification process is reserved for individuals with established reputation. - -Users can also be **affiliated** with a service if they're related to it, such as being an owner or part of the team. If you own a service and want to get verified, just reach out to us. - -## Listings - -### Suggesting a new listing - -To suggest a new listing, visit the [service suggestion form](/service-suggestion/new) and provide the most accurate information possible for higher chances to get approved. - -Once submitted, you get a unique tracking page where you can monitor its status and communicate directly with moderators. - -All new listings begin as **unlisted** — they're only accessible via direct link and won't appear in search results. After a brief admin review to confirm the request isn't spam or inappropriate, the listing will be marked as **Community Contributed**. - -### Suggestion Review Process - -#### First Review - -- A member of the **KYCnot.me team** reviews the submission to ensure it isn't spam or inappropriate. -- If the listing passes this initial check, it becomes **publicly visible** to all users. -- At this stage, the listing is **Community Contributed** and will show a disclaimer. - -#### Second Review (APPROVED) - -- The service is tested and investigated again. -- If it proves to be reliable, it is granted the `APPROVED` status, which means: - - The information is accurate. - - The service works as described (at the time of the testing). - - Basic functionality has been tested. - - Communication with the service's support was successful. - - A brief investigation found no obvious red flags. - -#### Final Review (VERIFIED) - -- After a period of no reported issues, the service will undergo a **third, comprehensive review**. - - The service is tested across different dates and under various conditions. - - The service administrators and support teams will be contacted for additional verification. -- If the service meets all requirements, it is granted the **`VERIFIED`** status. - -#### Failed Verifications - -If the data is not accurate, the service is a scam, or any other checks fail, the service will be rejected and will appear with a disclaimer. - -### Verification Steps - -Services will usually show the verification steps that the admins took to reach the verified (or not) status. Each step will have a description and some evidence attached. - -### Service Attributes - -An attribute is a feature of a service, categorized as: - -- **Good** – A positive feature -- **Warning** – A potential concern -- **Bad** – A significant drawback -- **Information** – Neutral details - -You can view all available attributes on the [Attributes page](/attributes). - -Attributes are classified into two main types: - -1. **Privacy Attributes** – Related to data protection and anonymity. -2. **Trust Attributes** – Related to reliability and security. - -These categories **directly influence** a service's Privacy and Trust scores, which contribute to its **overall rating**. - -### Service Scores - -Scores are calculated **automatically** using clear, fixed rules. We do not change or adjust scores by hand. The scoring system is **open-source** and anyone can review or suggest improvements. - -#### Privacy Score - -The privacy score measures how well a service protects user privacy, using a transparent, rules-based approach: - -1. **Base Score:** Every service starts with a neutral score of 50 points. -2. **KYC Level:** Adjusts the score based on the level of identity verification required: - - KYC Level 0 (No KYC): **+25 points** - - KYC Level 1 (Minimal KYC): **+10 points** - - KYC Level 2 (Moderate KYC): **-5 points** - - KYC Level 3 (More KYC): **-15 points** - - KYC Level 4 (Full mandatory KYC): **-25 points** -3. **Onion URL:** **+5 points** if the service offers at least one Onion (Tor) URL. -4. **I2P URL:** **+5 points** if the service offers at least one I2P URL. -5. **Monero Acceptance:** **+5 points** if the service accepts Monero as a payment method. -6. **Privacy Attributes:** The sum of all privacy points from attributes categorized as 'PRIVACY' is added to the score. -7. **Final Score Range:** The final score is always kept between 0 and 100. - -#### Trust Score - -The trust score represents how reliable and trustworthy a service is, based on objective, transparent criteria. - -1. **Base Score:** Every service begins with a neutral score of 50 points. -2. **Verification Status Adjustment:** - - **Verification Success:** +10 points - - **Approved:** +5 points - - **Community Contributed:** 0 points - - **Verification Failed (SCAM):** -50 points -3. **Trust Attributes:** The total trust points from all attributes categorized as 'TRUST' are added to the score. -4. **Recently Listed Penalty & Flag:** If a service was listed within the last 15 days and its status is `APPROVED`, a penalty of -10 points is applied to the trust score, and the service is flagged as recently listed. -5. **Final Score Range:** The final score is always kept between 0 and 100. - -#### Overall Score - -The overall score is calculated as `(privacy * 0.6) + (trust * 0.4)` and provides a combined measure of privacy and trust. - -### Terms of Service Reviews - -KYCnot.me automatically reviews and summarizes the Terms of Service (ToS) for every service monthly using AI. You get simple, clear summaries that highlight the most important points, so you can quickly see what matters. - -We hash each ToS document and only review it again if it changes. Some services may go a long time without a new review, but we still check and scrape their ToS every month. - -We aim for accuracy, but the AI may sometimes miss details or highlight less relevant information. If you see any error, contact us. - -### Events - -There are two types of events: - -- Automated events: Created by the system whenever something about a service changes, like its description, supported currencies, attributes, verification status... -- Manual events: Added by admins when there's important news, such as a service going offline, being hacked, acquired, shut down, or other major updates. - -You can also take a look at the [global timeline](/events) where you will find all the service's events sorted by date. - -### Reviews and Comments - -Reviews are comments with a one to five star rating for the service. Each user can leave only one review per service; new reviews replace the old one. - -You can also post regular comments to share your experience, ask questions, or discuss the service. - -If you've used the service, you can add an **order ID** or proof—this is only visible to admins for verification. You can also **flag** comments for issues like blocked funds or KYC requirements. - -Some reviews may be spam or fake. Read comments carefully and **always do your own research before making decisions**. - -#### Note on moderation - -**All comments are moderated.** First, an AI checks each comment. If nothing is flagged, the comment is published right away. If something seems off, the comment is held for a human to review. We only remove comments that are spam, nonsense, unsupported accusations, doxxing, or clear rule violations. - -To **see comments waiting for moderation**, toggle the switch in the comments section. These comments show up with a yellow background and a "pending" label. - -## Support the project - -If you like this project, you can support it through these methods: - -- Monero: - - `88V2Xi2mvcu3NdnHkVeZGyPtACg2w3iXZdUMJugUiPvFQHv5mVkih3o43ceVGz6cVs9uTBMt4MRMVW2xFgfGdh8DTCQ7vtp` - -## Contact - -You can contact via direct chat or via email. - -- [SimpleX Chat](https://simplex.chat/contact#/?v=2&smp=smp%3A%2F%2F0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU%3D%40smp8.simplex.im%2FcgKHYUYnpAIVoGb9lxb0qEMEpvYIvc1O%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAIW_JSq8wOsLKG4Xv4O54uT2D_l8MJBYKQIFj1FjZpnU%253D%26srv%3Dbeccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion) - -- If you use ProtonMail or Tutanota, you can have E2E encrypted communications with us directly. We also offer a [PGP Key](/pgp). Otherwise, we recommend reaching out via SimpleX chat for encrypted communications. - - [tuta.io](https://tuta.io) - - - [proton.me](https://proton.me) - - -## Disclaimer - -This website is strictly for informational purposes regarding privacy technology in the cryptocurrency space. We unequivocally condemn and do not endorse, support, or facilitate money laundering, terrorist financing, or any other illegal financial activities. The use of any information or service mentioned herein for such purposes is strictly prohibited and contrary to the core principles of this project. - -By using this website, you acknowledge and agree that you are solely responsible for your actions, due diligence, and compliance with all applicable laws. You use the information and any linked services entirely at your own risk. The operators of this website will not be held liable for any losses, damages, or legal consequences arising from your use of this site or any services listed herein. diff --git a/web/src/pages/access-denied.astro b/web/src/pages/access-denied.astro deleted file mode 100644 index 80bc343..0000000 --- a/web/src/pages/access-denied.astro +++ /dev/null @@ -1,75 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' -import { z } from 'astro:content' - -import BaseLayout from '../layouts/BaseLayout.astro' -import { zodParseQueryParamsStoringErrors } from '../lib/parseUrlFilters' -import { makeLoginUrl, makeUnimpersonateUrl } from '../lib/redirectUrls' - -const { - data: { reason, reasonType, redirect }, -} = zodParseQueryParamsStoringErrors( - { - reason: z.string().optional(), - reasonType: z.enum(['admin-required']).optional(), - redirect: z.string().optional(), - }, - Astro -) - -if (reasonType === 'admin-required' && Astro.locals.user?.admin) { - return Astro.redirect(redirect) -} ---- - - - -

- Access denied -

-

- {reason} -

- -
- - - Go to home - - - - Login as different user - - { - Astro.locals.actualUser && ( - - - Unimpersonate - - ) - } -
-
diff --git a/web/src/pages/account/edit.astro b/web/src/pages/account/edit.astro deleted file mode 100644 index b0a0db0..0000000 --- a/web/src/pages/account/edit.astro +++ /dev/null @@ -1,160 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' -import { actions, isInputError } from 'astro:actions' - -import { karmaUnlocksById } from '../../constants/karmaUnlocks' -import BaseLayout from '../../layouts/BaseLayout.astro' -import { makeKarmaUnlockMessage } from '../../lib/karmaUnlocks' -import { makeLoginUrl } from '../../lib/redirectUrls' - -const user = Astro.locals.user -if (!user) { - return Astro.redirect(makeLoginUrl(Astro.url, { message: 'Login to edit your profile' })) -} - -const result = Astro.getActionResult(actions.account.update) -if (result && !result.error) { - return Astro.redirect('/account') -} -const inputErrors = isInputError(result?.error) ? result.error.fields : {} ---- - - -
-
-

Edit Profile

- - - Back to Profile - -
- -
- - -
- - - { - inputErrors.displayName && ( -

{inputErrors.displayName.join(', ')}

- ) - } - { - !user.karmaUnlocks.displayName && ( -

- - {makeKarmaUnlockMessage(karmaUnlocksById.displayName)} - - Learn about karma - -

- ) - } -
- -
- - - {inputErrors.link &&

{inputErrors.link.join(', ')}

} - { - !user.karmaUnlocks.websiteLink && ( -

- - {makeKarmaUnlockMessage(karmaUnlocksById.websiteLink)} - - Learn about karma - -

- ) - } -
- -
- -
- -

- Upload a square image for best results. Supported formats: JPG, PNG, WebP, AVIF, JXL. Max size: - 5MB. -

-
- { - inputErrors.pictureFile && ( -

{inputErrors.pictureFile.join(', ')}

- ) - } - { - !user.karmaUnlocks.profilePicture && ( -

- - You need 200 karma to have a profile picture. - - Learn about karma - -

- ) - } -
- -
- - - Cancel - -
-
-
-
diff --git a/web/src/pages/account/generate.astro b/web/src/pages/account/generate.astro deleted file mode 100644 index 15ccd80..0000000 --- a/web/src/pages/account/generate.astro +++ /dev/null @@ -1,117 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' -import { actions } from 'astro:actions' - -import Button from '../../components/Button.astro' -import Captcha from '../../components/Captcha.astro' -import InputHoneypotTrap from '../../components/InputHoneypotTrap.astro' -import MiniLayout from '../../layouts/MiniLayout.astro' -import { callActionWithObject } from '../../lib/callActionWithUrlParams' -import { prettifyUserSecretToken } from '../../lib/userSecretToken' - -const generateResult = Astro.getActionResult(actions.account.generate) -if (generateResult && !generateResult.error) { - return Astro.rewrite('/account/welcome') -} - -const data = await callActionWithObject(Astro, actions.account.preGenerateToken, undefined, 'form') - -const preGeneratedToken = data?.token -const prettyToken = preGeneratedToken ? prettifyUserSecretToken(preGeneratedToken) : undefined ---- - -{/* eslint-disable astro/jsx-a11y/no-autofocus */} - - - { - Astro.locals.user && ( -
- -

You will be logged out of your current account.

-
- ) - } - -
- {/* Hack to make password managers suggest saving the secret token */} -
- - -
- - - - - - - -
- - - - -
-
-

- - Service Affiliations -

-
- - { - user.serviceAffiliations.length > 0 ? ( - - ) : ( -

No service affiliations yet.

- ) - } -
- -
-
-

- - Karma Unlocks -

- -
-

- Earn karma to unlock features and privileges. Learn about karma -

-
-
- -
-
-

Positive unlocks

- - { - sortBy( - karmaUnlocks.filter((unlock) => unlock.karma >= 0), - 'karma' - ).map((unlock) => ( -
-
- - - -
-

- {unlock.name} -

-

{unlock.karma.toLocaleString()} karma

-
-
-
- {user.karmaUnlocks[unlock.id] ? ( - - Unlocked - - ) : ( - - Locked - - )} -
-
- )) - } -
- -
-

Negative unlocks

- - { - sortBy( - karmaUnlocks.filter((unlock) => unlock.karma < 0), - 'karma' - ) - .reverse() - .map((unlock) => ( -
-
- - - -
-

- {unlock.name} -

-

{unlock.karma.toLocaleString()} karma

-
-
-
- {user.karmaUnlocks[unlock.id] ? ( - - Active - - ) : ( - - Avoided - - )} -
-
- )) - } - -

- - Negative karma leads to restrictions. Keep interactions positive to - avoid penalties. -

-
-
-
- -
-
-
-

- - Recent Comments -

- {user._count.comments.toLocaleString()} comments -
- - { - user.comments.length === 0 ? ( -

No comments yet.

- ) : ( -
- - - - - - - - - - - - {user.comments.map((comment) => ( - - - - - - - - ))} - -
ServiceCommentStatusUpvotesDate
- - {comment.service.name} - - -

{comment.content}

-
- - {comment.status} - - - - {comment.upvotes} - - - -
-
- ) - } -
- - { - user.karmaUnlocks.voteComments || user._count.commentVotes ? ( -
-
-

- - Recent Votes -

- {user._count.commentVotes.toLocaleString()} votes -
- - {user.commentVotes.length === 0 ? ( -

No votes yet.

- ) : ( -
- - - - - - - - - - - {user.commentVotes.map((vote) => ( - - - - - - - ))} - -
ServiceCommentVoteDate
- - {vote.comment.service.name} - - -

{vote.comment.content}

-
- {vote.downvote ? ( - - - - ) : ( - - - - )} - - -
-
- )} -
- ) : ( -
-

- - Recent Votes -

- - - Locked - -
- ) - } - -
-
-

- - Recent Suggestions -

- - View all - -
- - { - user.suggestions.length === 0 ? ( -

No suggestions yet.

- ) : ( -
- - - - - - - - - - - {user.suggestions.map((suggestion) => { - const typeInfo = getServiceSuggestionTypeInfo(suggestion.type) - const statusInfo = getServiceSuggestionStatusInfo(suggestion.status) - - return ( - - - - - - - ) - })} - -
ServiceTypeStatusDate
- - {suggestion.service.name} - - - - - {typeInfo.label} - - - - - {statusInfo.label} - - - -
-
- ) - } -
- -
-
-

- - Recent Karma Transactions -

- {user.totalKarma.toLocaleString()} karma -
- - { - user.karmaTransactions.length === 0 ? ( -

No karma transactions yet.

- ) : ( -
- - - - - - - - - - - {user.karmaTransactions.map((transaction) => ( - - - - - - - ))} - -
ActionDescriptionPointsDate
{transaction.action}{transaction.description}= 0 ? 'text-green-400' : 'text-red-400' - )} - > - {transaction.points >= 0 && '+'} - {transaction.points} - - {new Date(transaction.createdAt).toLocaleDateString()} -
-
- ) - } -
-
- - - diff --git a/web/src/pages/account/login.astro b/web/src/pages/account/login.astro deleted file mode 100644 index cf00f2f..0000000 --- a/web/src/pages/account/login.astro +++ /dev/null @@ -1,82 +0,0 @@ ---- -import { actions } from 'astro:actions' - -import Button from '../../components/Button.astro' -import InputLoginToken from '../../components/InputLoginToken.astro' -import MiniLayout from '../../layouts/MiniLayout.astro' -import { logout } from '../../lib/userCookies' - -const result = Astro.getActionResult(actions.account.login) -if (result && !result.error) { - return Astro.redirect(result.data.redirect) -} - -if (Astro.url.searchParams.get('logout')) { - await logout(Astro) - const url = new URL(Astro.url) - url.searchParams.delete('logout') - return Astro.redirect(url.toString()) -} - -// Redirect if already logged in -if (Astro.locals.user) { - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - return Astro.redirect(Astro.url.searchParams.get('redirect') || '/') -} - -const message = Astro.url.searchParams.get('message') ---- - - -
- {/* eslint-disable-next-line astro/jsx-a11y/no-autofocus */} - - - - - - - - - - -
-
-
- - -
-
- - -
-
- -
- - -
-
-
-
- - -
-
-

Attributes List

-
- Scroll horizontally to see more → -
-
- -
-
- - - - - - - - - - - - - - { - attributesWithDetails.map((attribute, index) => ( - <> - - - - - - - - - - - - - - )) - } - -
- - Title - - - - Category - - - - Type - - - - - Priv - - - - - - Tr - - - - - - Svcs - - - - Actions -
-
- {attribute.title} -
-
{attribute.slug}
-
- - - {attribute.categoryInfo.label} - - - - - {attribute.typeInfo.label} - - - 0, - 'text-zinc-500': attribute.privacyPoints === 0, - })} - > - {formatNumber(attribute.privacyPoints, { showSign: true })} - - - 0, - 'text-zinc-500': attribute.trustPoints === 0, - })} - > - {formatNumber(attribute.trustPoints, { showSign: true })} - - - - {attribute.serviceCount} - - -
- -
- - -
-
-
-
-
-
- - - - - diff --git a/web/src/pages/admin/comments.astro b/web/src/pages/admin/comments.astro deleted file mode 100644 index ba41076..0000000 --- a/web/src/pages/admin/comments.astro +++ /dev/null @@ -1,237 +0,0 @@ ---- -import { z } from 'astro/zod' - -import CommentModeration from '../../components/CommentModeration.astro' -import { - commentStatusFilters, - commentStatusFiltersZodEnum, - getCommentStatusFilterInfo, - getCommentStatusFilterValue, -} from '../../constants/commentStatusFilters' -import BaseLayout from '../../layouts/BaseLayout.astro' -import { cn } from '../../lib/cn' -import { zodParseQueryParamsStoringErrors } from '../../lib/parseUrlFilters' -import { prisma } from '../../lib/prisma' -import { urlWithParams } from '../../lib/urls' - -const user = Astro.locals.user -if (!user || (!user.admin && !user.verifier)) { - return Astro.rewrite('/404') -} - -const { data: params } = zodParseQueryParamsStoringErrors( - { - status: commentStatusFiltersZodEnum.default('all'), - page: z.number().int().positive().default(1), - }, - Astro -) -const PAGE_SIZE = 20 - -const statusFilter = getCommentStatusFilterInfo(params.status) - -const [comments = [], totalComments = 0] = await Astro.locals.banners.try( - 'Error fetching comments', - async () => - prisma.comment.findManyAndCount({ - where: statusFilter.whereClause, - include: { - author: true, - service: { - select: { - name: true, - slug: true, - }, - }, - parent: { - include: { - author: true, - }, - }, - votes: true, - }, - orderBy: [{ createdAt: 'desc' }, { id: 'asc' }], - skip: (params.page - 1) * PAGE_SIZE, - take: PAGE_SIZE, - }), - [] -) -const totalPages = Math.ceil(totalComments / PAGE_SIZE) ---- - - -
-

> comments.moderate

- - -
- { - commentStatusFilters.map((filter) => ( - - {filter.label} - - )) - } -
-
- - -
- { - comments - .map((comment) => ({ - ...comment, - statusFilterInfo: getCommentStatusFilterInfo(getCommentStatusFilterValue(comment)), - })) - .map((comment) => ( -
-
- {/* Author Info */} - {comment.author.name} - {comment.author.admin && ( - - admin - - )} - {comment.author.verified && !comment.author.admin && ( - - verified - - )} - {comment.author.verifier && !comment.author.admin && ( - - verifier - - )} - - {/* Service Link */} - - - {comment.service.name} - - - {/* Date */} - - {new Date(comment.createdAt).toLocaleString()} - - {/* Status Badges */} - {comment.statusFilterInfo.label} -
- - {/* Parent Comment Context */} - {comment.parent && ( -
-
Replying to {comment.parent.author.name}:
-
{comment.parent.content}
-
- )} - - {/* Comment Content */} -
{comment.content}
- - {/* Notes */} - {comment.communityNote && ( -
-
Community Note
-

{comment.communityNote}

-
- )} - {comment.internalNote && ( -
-
Internal Note
-

{comment.internalNote}

-
- )} - {comment.privateContext && ( -
-
Private Context
-

{comment.privateContext}

-
- )} - - {comment.orderId && ( -
-
Order ID
-
-

{comment.orderId}

- {comment.orderIdStatus && ( - - {comment.orderIdStatus} - - )} -
-
- )} - - {(comment.kycRequested || comment.fundsBlocked) && ( -
-
Issue Flags
-
- {comment.kycRequested && ( - - KYC Requested - - )} - {comment.fundsBlocked && ( - - Funds Blocked - - )} -
-
- )} - - -
- )) - } -
- - - { - totalPages > 1 && ( -
- {params.page > 1 && ( - - Previous - - )} - - Page {params.page} of {totalPages} - - {params.page < totalPages && ( - - Next - - )} -
- ) - } -
diff --git a/web/src/pages/admin/index.astro b/web/src/pages/admin/index.astro deleted file mode 100644 index bb2124b..0000000 --- a/web/src/pages/admin/index.astro +++ /dev/null @@ -1,76 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' - -import BaseLayout from '../../layouts/BaseLayout.astro' - -import type { ComponentProps } from 'astro/types' - -type AdminLink = { - icon: ComponentProps['name'] - title: string - href: string - description: string -} - -const adminLinks: AdminLink[] = [ - { - icon: 'ri:box-3-line', - title: 'Services', - href: '/admin/services', - description: 'Manage your available services', - }, - { - icon: 'ri:file-list-3-line', - title: 'Attributes', - href: '/admin/attributes', - description: 'Configure service attributes', - }, - { - icon: 'ri:user-3-line', - title: 'Users', - href: '/admin/users', - description: 'Manage user accounts', - }, - { - icon: 'ri:chat-settings-line', - title: 'Comments', - href: '/admin/comments', - description: 'Moderate user comments', - }, - { - icon: 'ri:lightbulb-line', - title: 'Service suggestions', - href: '/admin/service-suggestions', - description: 'Review and manage service suggestions', - }, -] ---- - - -

- - Admin Dashboard -

- -
- { - adminLinks.map((link) => ( - -
- -

- {link.title} -

-
-

{link.description}

-
- )) - } -
-
diff --git a/web/src/pages/admin/service-suggestions/[id].astro b/web/src/pages/admin/service-suggestions/[id].astro deleted file mode 100644 index ecfb6ee..0000000 --- a/web/src/pages/admin/service-suggestions/[id].astro +++ /dev/null @@ -1,195 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' -import { actions } from 'astro:actions' - -import Chat from '../../../components/Chat.astro' -import ServiceCard from '../../../components/ServiceCard.astro' -import { getServiceSuggestionStatusInfo } from '../../../constants/serviceSuggestionStatus' -import BaseLayout from '../../../layouts/BaseLayout.astro' -import { cn } from '../../../lib/cn' -import { parseIntWithFallback } from '../../../lib/numbers' -import { prisma } from '../../../lib/prisma' -import { makeLoginUrl } from '../../../lib/redirectUrls' - -const user = Astro.locals.user -if (!user?.admin) { - return Astro.redirect(makeLoginUrl(Astro.url, { message: 'Admin access required' })) -} - -const { id: serviceSuggestionIdRaw } = Astro.params -const serviceSuggestionId = parseIntWithFallback(serviceSuggestionIdRaw) -if (!serviceSuggestionId) { - return Astro.rewrite('/404') -} - -const serviceSuggestion = await Astro.locals.banners.try('Error fetching service suggestion', async () => - prisma.serviceSuggestion.findUnique({ - where: { - id: serviceSuggestionId, - }, - select: { - id: true, - status: true, - notes: true, - createdAt: true, - type: true, - user: { - select: { - id: true, - name: true, - }, - }, - service: { - select: { - id: true, - name: true, - slug: true, - description: true, - overallScore: true, - kycLevel: true, - imageUrl: true, - verificationStatus: true, - acceptedCurrencies: true, - categories: { - select: { - name: true, - icon: true, - }, - }, - }, - }, - messages: { - select: { - id: true, - content: true, - createdAt: true, - user: { - select: { - id: true, - name: true, - picture: true, - }, - }, - }, - orderBy: { - createdAt: 'desc', - }, - }, - }, - }) -) - -if (!serviceSuggestion) { - return Astro.rewrite('/404') -} - -const statusInfo = getServiceSuggestionStatusInfo(serviceSuggestion.status) ---- - - -
- - - Back - - -

Service Suggestion

-
- -
-
- -
- -
-

Suggestion Details

- -
- Status: - - - {statusInfo.label} - - - Submitted by: - - - {serviceSuggestion.user.name} - - - - Submitted at: - {serviceSuggestion.createdAt.toLocaleString()} - - Service page: - - View Service - -
- - { - serviceSuggestion.notes && ( -
-

Notes from user:

-
- {serviceSuggestion.notes} -
-
- ) - } -
-
- -
-
-

Messages

- -
- - - -
-
- - -
-
diff --git a/web/src/pages/admin/service-suggestions/index.astro b/web/src/pages/admin/service-suggestions/index.astro deleted file mode 100644 index db8b5e2..0000000 --- a/web/src/pages/admin/service-suggestions/index.astro +++ /dev/null @@ -1,385 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' -import { actions } from 'astro:actions' -import { z } from 'astro:content' -import { orderBy as lodashOrderBy } from 'lodash-es' - -import SortArrowIcon from '../../../components/SortArrowIcon.astro' -import TimeFormatted from '../../../components/TimeFormatted.astro' -import { - getServiceSuggestionStatusInfo, - serviceSuggestionStatuses, -} from '../../../constants/serviceSuggestionStatus' -import BaseLayout from '../../../layouts/BaseLayout.astro' -import { zodParseQueryParamsStoringErrors } from '../../../lib/parseUrlFilters' -import { prisma } from '../../../lib/prisma' -import { makeLoginUrl } from '../../../lib/redirectUrls' - -import type { Prisma, ServiceSuggestionStatus } from '@prisma/client' - -const user = Astro.locals.user -if (!user?.admin) { - return Astro.redirect(makeLoginUrl(Astro.url, { message: 'Admin access required' })) -} - -const search = Astro.url.searchParams.get('search') ?? '' -const statusEnumValues = serviceSuggestionStatuses.map((s) => s.value) as [string, ...string[]] -const statusParam = Astro.url.searchParams.get('status') -const statusFilter = z - .enum(statusEnumValues) - .nullable() - .parse(statusParam === '' ? null : statusParam) as ServiceSuggestionStatus | null - -const { data: filters } = zodParseQueryParamsStoringErrors( - { - 'sort-by': z.enum(['service', 'status', 'user', 'createdAt', 'messageCount']).default('createdAt'), - 'sort-order': z.enum(['asc', 'desc']).default('desc'), - }, - Astro -) - -const sortBy = filters['sort-by'] -const sortOrder = filters['sort-order'] - -let prismaOrderBy: Prisma.ServiceSuggestionOrderByWithRelationInput = { createdAt: 'desc' } -if (sortBy === 'createdAt') { - prismaOrderBy = { createdAt: sortOrder } -} - -let suggestions = await prisma.serviceSuggestion.findMany({ - where: { - ...(search - ? { - OR: [ - { service: { name: { contains: search, mode: 'insensitive' } } }, - { user: { name: { contains: search, mode: 'insensitive' } } }, - { notes: { contains: search, mode: 'insensitive' } }, - ], - } - : {}), - status: statusFilter ?? undefined, - }, - orderBy: prismaOrderBy, - select: { - id: true, - status: true, - notes: true, - createdAt: true, - user: { - select: { - id: true, - name: true, - }, - }, - service: { - select: { - id: true, - name: true, - slug: true, - description: true, - imageUrl: true, - verificationStatus: true, - categories: { - select: { - name: true, - icon: true, - }, - }, - }, - }, - messages: { - select: { - id: true, - content: true, - createdAt: true, - user: { - select: { - id: true, - name: true, - }, - }, - }, - orderBy: { - createdAt: 'desc', - }, - take: 1, - }, - _count: { - select: { - messages: true, - }, - }, - }, -}) - -let suggestionsWithDetails = suggestions.map((s) => ({ - ...s, - statusInfo: getServiceSuggestionStatusInfo(s.status), - messageCount: s._count.messages, - lastMessage: s.messages[0], -})) - -if (sortBy === 'service') { - suggestionsWithDetails = lodashOrderBy( - suggestionsWithDetails, - [(s) => s.service.name.toLowerCase()], - [sortOrder] - ) -} else if (sortBy === 'status') { - suggestionsWithDetails = lodashOrderBy(suggestionsWithDetails, [(s) => s.statusInfo.label], [sortOrder]) -} else if (sortBy === 'user') { - suggestionsWithDetails = lodashOrderBy( - suggestionsWithDetails, - [(s) => s.user.name.toLowerCase()], - [sortOrder] - ) -} else if (sortBy === 'messageCount') { - suggestionsWithDetails = lodashOrderBy(suggestionsWithDetails, ['messageCount'], [sortOrder]) -} - -const suggestionCount = suggestionsWithDetails.length - -const makeSortUrl = (slug: string) => { - const currentSortBy = filters['sort-by'] - const currentSortOrder = filters['sort-order'] - const newSortOrder = currentSortBy === slug && currentSortOrder === 'asc' ? 'desc' : 'asc' - const searchParams = new URLSearchParams(Astro.url.search) - searchParams.set('sort-by', slug) - searchParams.set('sort-order', newSortOrder) - return `/admin/service-suggestions?${searchParams.toString()}` -} ---- - - -
-

Service Suggestions

-
- {suggestionCount} suggestions -
-
- -
-
-
- - -
-
- - -
-
- -
-
-
- -
-
-

Suggestions List

-
- Scroll horizontally to see more → -
-
-
-
- - - - - - - - - - - - - { - suggestionsWithDetails.map((suggestion) => ( - - - - - - - - - )) - } - -
- - Service - - - - User - - - - Status - - - - Created - - - - Messages - - - Actions -
- -
- {suggestion.service.description} -
-
- - {suggestion.user.name} - - -
- - -
-
- - - - {suggestion.messageCount} - - -
- - - -
-
-
-
-
-
- - diff --git a/web/src/pages/admin/services/[slug]/edit.astro b/web/src/pages/admin/services/[slug]/edit.astro deleted file mode 100644 index ceba794..0000000 --- a/web/src/pages/admin/services/[slug]/edit.astro +++ /dev/null @@ -1,1367 +0,0 @@ ---- -import { - AttributeCategory, - Currency, - EventType, - VerificationStatus, - VerificationStepStatus, -} from '@prisma/client' -import { Icon } from 'astro-icon/components' -import { actions, isInputError } from 'astro:actions' -import { Image } from 'astro:assets' - -import { serviceVisibilities } from '../../../../constants/serviceVisibility' -import BaseLayout from '../../../../layouts/BaseLayout.astro' -import { cn } from '../../../../lib/cn' -import { prisma } from '../../../../lib/prisma' -import { ACCEPTED_IMAGE_TYPES } from '../../../../lib/zodUtils' - -const { slug } = Astro.params - -const serviceResult = Astro.getActionResult(actions.admin.service.update) -const eventCreateResult = Astro.getActionResult(actions.admin.event.create) -const eventToggleResult = Astro.getActionResult(actions.admin.event.toggle) -const eventDeleteResult = Astro.getActionResult(actions.admin.event.delete) -const eventUpdateResult = Astro.getActionResult(actions.admin.event.update) -const verificationStepCreateResult = Astro.getActionResult(actions.admin.verificationStep.create) -const verificationStepUpdateResult = Astro.getActionResult(actions.admin.verificationStep.update) -const verificationStepDeleteResult = Astro.getActionResult(actions.admin.verificationStep.delete) - -Astro.locals.banners.addIfSuccess(serviceResult, 'Service updated successfully') -Astro.locals.banners.addIfSuccess(eventCreateResult, 'Event created successfully') -Astro.locals.banners.addIfSuccess(eventToggleResult, 'Event visibility updated successfully') -Astro.locals.banners.addIfSuccess(eventDeleteResult, 'Event deleted successfully') -Astro.locals.banners.addIfSuccess(eventUpdateResult, 'Event updated successfully') -Astro.locals.banners.addIfSuccess(verificationStepCreateResult, 'Verification step added successfully') -Astro.locals.banners.addIfSuccess(verificationStepUpdateResult, 'Verification step updated successfully') -Astro.locals.banners.addIfSuccess(verificationStepDeleteResult, 'Verification step deleted successfully') - -if (serviceResult && !serviceResult.error && slug !== serviceResult.data.service.slug) { - return Astro.redirect(`/admin/services/${serviceResult.data.service.slug}/edit`) -} - -const serviceInputErrors = isInputError(serviceResult?.error) ? serviceResult.error.fields : {} -const eventInputErrors = isInputError(eventCreateResult?.error) ? eventCreateResult.error.fields : {} -const eventUpdateInputErrors = isInputError(eventUpdateResult?.error) ? eventUpdateResult.error.fields : {} -const verificationStepInputErrors = isInputError(verificationStepCreateResult?.error) - ? verificationStepCreateResult.error.fields - : {} -const verificationStepUpdateInputErrors = isInputError(verificationStepUpdateResult?.error) - ? verificationStepUpdateResult.error.fields - : {} - -if (!slug) return Astro.rewrite('/404') - -const service = await Astro.locals.banners.try('Error fetching service', () => - prisma.service.findUnique({ - where: { slug }, - include: { - attributes: { - select: { - attribute: { - select: { - id: true, - }, - }, - }, - }, - categories: { - select: { - id: true, - }, - }, - events: { - orderBy: { - startedAt: 'desc', - }, - }, - verificationRequests: { - select: { - id: true, - user: { - select: { - id: true, - name: true, - displayName: true, - }, - }, - createdAt: true, - }, - orderBy: { - createdAt: 'desc', - }, - }, - verificationSteps: { - orderBy: { - createdAt: 'desc', - }, - }, - contactMethods: { - orderBy: { - label: 'asc', - }, - }, - _count: { - select: { - verificationRequests: true, - }, - }, - }, - }) -) - -if (!service) return Astro.rewrite('/404') - -const categories = await Astro.locals.banners.try( - 'Error fetching categories', - () => - prisma.category.findMany({ - orderBy: { name: 'asc' }, - }), - [] -) - -const attributes = await Astro.locals.banners.try( - 'Error fetching attributes', - () => - prisma.attribute.findMany({ - orderBy: { category: 'asc' }, - }), - [] -) - -const inputBaseClasses = - 'w-full rounded-md border-zinc-600 bg-zinc-700/80 p-2 text-zinc-200 placeholder-zinc-400 focus:border-sky-500 focus:ring-1 focus:ring-sky-500 text-sm' -const labelBaseClasses = 'block text-sm font-medium text-zinc-300 mb-1' -const errorTextClasses = 'mt-1 text-xs text-red-400' -const checkboxLabelClasses = 'inline-flex items-center text-sm text-zinc-300' -const checkboxInputClasses = - 'rounded-sm border-zinc-500 bg-zinc-700 text-sky-500 focus:ring-sky-500 focus:ring-offset-zinc-800' - -// Button style constants -const buttonPrimaryClasses = - 'inline-flex items-center justify-center rounded-md border border-transparent bg-sky-600 px-3 py-1.5 text-sm font-medium text-white shadow-sm hover:bg-sky-700 focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2 focus:ring-offset-zinc-900' - -const buttonSmallBaseClasses = 'rounded-md px-2 py-1 text-xs font-medium' -const buttonSmallPrimaryClasses = cn( - buttonSmallBaseClasses, - 'text-sky-400 hover:bg-sky-700/30 hover:text-sky-300' -) -const buttonSmallSecondaryClasses = cn( - buttonSmallBaseClasses, - 'text-zinc-400 hover:bg-zinc-700/50 hover:text-zinc-300' -) -const buttonSmallDestructiveClasses = cn( - buttonSmallBaseClasses, - 'text-red-400 hover:bg-red-700/30 hover:text-red-300' -) -const buttonSmallWarningClasses = cn( - buttonSmallBaseClasses, - 'text-yellow-400 hover:bg-yellow-700/30 hover:text-yellow-300' -) ---- - - -
-
-

- Editing {service.name} [{service.id}] -

- - - View Service Page - -
- -
-

- Service Details -

-
- -
- - - {serviceInputErrors.name &&

{serviceInputErrors.name.join(', ')}

} -
- -
- - - { - serviceInputErrors.description && ( -

{serviceInputErrors.description.join(', ')}

- ) - } -
- -
- - - {serviceInputErrors.slug &&

{serviceInputErrors.slug.join(', ')}

} -
- -
-
- - - { - serviceInputErrors.serviceUrls && ( -

{serviceInputErrors.serviceUrls.join(', ')}

- ) - } -
-
- - - { - serviceInputErrors.tosUrls && ( -

{serviceInputErrors.tosUrls.join(', ')}

- ) - } -
-
- - - { - serviceInputErrors.onionUrls && ( -

{serviceInputErrors.onionUrls.join(', ')}

- ) - } -
-
- - - {/* Assuming i2pUrls might have errors, add error display if schema supports it */} - { - /* serviceInputErrors.i2pUrls && ( -

{serviceInputErrors.i2pUrls.join(', ')}

- ) */ - } -
-
- -
-
-
- -
- -

- Leave empty to keep the current image. Square images (e.g., 256x256) recommended. -

-
- { - serviceInputErrors.imageFile && ( -

{serviceInputErrors.imageFile.join(', ')}

- ) - } -
- { - service.imageUrl ? ( -
- Current service image -
- ) : ( -
- -
- ) - } -
-
- -
-
- -
- { - categories.map((category) => ( - - )) - } -
- { - serviceInputErrors.categories && ( -

{serviceInputErrors.categories.join(', ')}

- ) - } -
- -
- - - { - serviceInputErrors.kycLevel && ( -

{serviceInputErrors.kycLevel.join(', ')}

- ) - } -
-
- -
- -
- { - Object.values(AttributeCategory).map((categoryValue) => ( -
-

{categoryValue}

-
- {attributes - .filter((attr) => attr.category === categoryValue) - .map((attr) => ( - - ))} -
- {serviceInputErrors.attributes && ( -

{serviceInputErrors.attributes.join(', ')}

- )} -
- )) - } -
-
- -
-
- - - { - serviceInputErrors.verificationStatus && ( -

{serviceInputErrors.verificationStatus.join(', ')}

- ) - } -
- -
- -
- { - Object.values(Currency).map((currency) => ( - - )) - } -
- { - serviceInputErrors.acceptedCurrencies && ( -

{serviceInputErrors.acceptedCurrencies.join(', ')}

- ) - } -
-
- -
- - - { - serviceInputErrors.verificationSummary && ( -

{serviceInputErrors.verificationSummary.join(', ')}

- ) - } -
- -
- - - { - serviceInputErrors.verificationProofMd && ( -

{serviceInputErrors.verificationProofMd.join(', ')}

- ) - } -
- -
- - - { - serviceInputErrors.referral && ( -

{serviceInputErrors.referral.join(', ')}

- ) - } -
- -
- -
- { - serviceVisibilities.map((visibility) => ( -
- - -
- )) - } -
- { - serviceInputErrors.serviceVisibility && ( -

{serviceInputErrors.serviceVisibility.join(', ')}

- ) - } -
- - -
-
- - -
-

- Events -

-
- - { - service.events.length > 0 && ( -
-

Existing Events

- {service.events.map((event) => ( -
-
-
-
- {event.title} - - {event.type} - - {!event.visible && ( - - Hidden - - )} -
-

{event.content}

-
- Started: {new Date(event.startedAt).toLocaleDateString()} - - Ended: - {event.endedAt - ? event.endedAt === event.startedAt - ? '1-time event' - : new Date(event.endedAt).toLocaleDateString() - : 'Ongoing'} - - {event.source && Source: {event.source}} -
-
-
-
- - -
- -
- - -
-
-
- {/* Edit Event Form - Hidden by default */} - -
- ))} -
- ) - } - -
- -

- - Add New Event -

-
-
- -
- - - {eventInputErrors.title &&

{eventInputErrors.title.join(', ')}

} -
-
- - - { - eventInputErrors.content && ( -

{eventInputErrors.content.join(', ')}

- ) - } -
-
-
- - - { - eventInputErrors.startedAt && ( -

{eventInputErrors.startedAt.join(', ')}

- ) - } -
-
- - - { - eventInputErrors.endedAt && ( -

{eventInputErrors.endedAt.join(', ')}

- ) - } -
-
-
-

End Date Options:

-
    -
  • Leave empty: Event is ongoing.
  • -
  • Same as start date: One-time event.
  • -
  • Future date: Event with specific end date.
  • -
-
-
-
- - - { - eventInputErrors.source && ( -

{eventInputErrors.source.join(', ')}

- ) - } -
-
- - - {eventInputErrors.type &&

{eventInputErrors.type.join(', ')}

} -
-
- -
-
-
-
- - -
-

- Verification Steps -

-
- - { - service.verificationSteps.length > 0 && ( -
-

Submitted Steps

- {service.verificationSteps.map((step) => ( -
-
-
-
- {step.title} - - {step.status.replace('_', ' ')} - -
-

{step.description}

- {step.evidenceMd && ( -

Evidence provided (see edit form)

- )} -

- Created: {new Date(step.createdAt).toLocaleDateString()} -

-
-
- -
- - -
-
-
- - {/* Edit Verification Step Form - Hidden by default */} - -
- ))} -
- ) - } - -
- -

- - Add New Verification Step -

-
-
- -
- - - { - verificationStepInputErrors.title && ( -

{verificationStepInputErrors.title.join(', ')}

- ) - } -
-
- - - { - verificationStepInputErrors.description && ( -

{verificationStepInputErrors.description.join(', ')}

- ) - } -
-
- - - { - verificationStepInputErrors.evidenceMd && ( -

{verificationStepInputErrors.evidenceMd.join(', ')}

- ) - } -
-
- - - { - verificationStepInputErrors.status && ( -

{verificationStepInputErrors.status.join(', ')}

- ) - } -
- -
-
-
-
- -
-

- Verification Requests - - {service._count.verificationRequests} - -

-
- { - service.verificationRequests.length > 0 ? ( -
- - - - - - - - - {service.verificationRequests.map((request) => ( - - - - - ))} - -
UserRequested At
{request.user.displayName ?? request.user.name}{new Date(request.createdAt).toLocaleString()}
-
- ) : ( -

No verification requests yet.

- ) - } -
-
- - -
-

- Contact Methods -

-
- - { - service.contactMethods.length > 0 && ( -
-

Existing Contact Methods

- {service.contactMethods.map((method) => ( -
-
-
-
- - {method.label} -
-

{method.value}

- {method.info &&

{method.info}

} -
-
- -
- - -
-
-
- - {/* Edit Contact Method Form - Hidden by default */} - -
- ))} -
- ) - } - -
- -

- - Add New Contact Method -

-
-
- -
- - -
-
- - -
-
- - -
-
- - -
- -
-
-
-
-
diff --git a/web/src/pages/admin/services/index.astro b/web/src/pages/admin/services/index.astro deleted file mode 100644 index 2a51f56..0000000 --- a/web/src/pages/admin/services/index.astro +++ /dev/null @@ -1,616 +0,0 @@ ---- -import { ServiceVisibility, VerificationStatus, type Prisma } from '@prisma/client' -import { z } from 'astro/zod' -import { Icon } from 'astro-icon/components' -import { Image } from 'astro:assets' - -import defaultImage from '../../../assets/fallback-service-image.jpg' -import SortArrowIcon from '../../../components/SortArrowIcon.astro' -import { getKycLevelInfo } from '../../../constants/kycLevels' -import { getVerificationStatusInfo } from '../../../constants/verificationStatus' -import BaseLayout from '../../../layouts/BaseLayout.astro' -import { cn } from '../../../lib/cn' -import { zodParseQueryParamsStoringErrors } from '../../../lib/parseUrlFilters' -import { prisma } from '../../../lib/prisma' - -const { data: filters } = zodParseQueryParamsStoringErrors( - { - search: z.string().optional(), - verificationStatus: z.nativeEnum(VerificationStatus).optional(), - visibility: z.nativeEnum(ServiceVisibility).optional(), - sort: z.enum(['name', 'createdAt', 'overallScore', 'verificationRequests']).default('name'), - order: z.enum(['asc', 'desc']).default('asc'), - page: z.coerce.number().int().positive().optional().default(1), - }, - Astro -) - -const itemsPerPage = 20 - -const sortProperty = filters.sort -const sortDirection = filters.order - -let orderBy: Prisma.ServiceOrderByWithRelationInput - -switch (sortProperty) { - case 'verificationRequests': - orderBy = { verificationRequests: { _count: sortDirection } } - break - case 'createdAt': // createdAt can be ambiguous without a specific direction - case 'name': - case 'overallScore': - orderBy = { [sortProperty]: sortDirection } - break - default: - orderBy = { name: 'asc' } // Default sort -} - -const whereClause: Prisma.ServiceWhereInput = { - ...(filters.search - ? { - OR: [ - { name: { contains: filters.search, mode: 'insensitive' } }, - { description: { contains: filters.search, mode: 'insensitive' } }, - { serviceUrls: { has: filters.search } }, - { tosUrls: { has: filters.search } }, - { onionUrls: { has: filters.search } }, - { i2pUrls: { has: filters.search } }, - ], - } - : {}), - verificationStatus: filters.verificationStatus ?? undefined, - serviceVisibility: filters.visibility ?? undefined, -} - -const totalServicesCount = await Astro.locals.banners.try( - 'Error counting services', - async () => prisma.service.count({ where: whereClause }), - 0 -) - -const totalPages = Math.ceil(totalServicesCount / itemsPerPage) -const validPage = Math.max(1, Math.min(filters.page, totalPages || 1)) -const skip = (validPage - 1) * itemsPerPage - -const services = await Astro.locals.banners.try( - 'Error fetching services', - async () => - prisma.service.findMany({ - where: whereClause, - select: { - id: true, - name: true, - slug: true, - description: true, - kycLevel: true, - overallScore: true, - privacyScore: true, - trustScore: true, - verificationStatus: true, - imageUrl: true, - serviceUrls: true, - tosUrls: true, - onionUrls: true, - i2pUrls: true, - serviceVisibility: true, - createdAt: true, - categories: { - select: { - id: true, - name: true, - icon: true, - }, - }, - attributes: { - include: { - attribute: true, - }, - }, - _count: { - select: { - verificationRequests: true, - }, - }, - }, - orderBy, - take: itemsPerPage, - skip, - }), - [] -) - -const servicesWithInfo = services.map((service) => { - const verificationStatusInfo = getVerificationStatusInfo(service.verificationStatus) - const kycLevelInfo = getKycLevelInfo(String(service.kycLevel)) - - return { - ...service, - verificationStatusInfo, - kycLevelInfo, - kycColor: - service.kycLevel === 0 - ? '22, 163, 74' // green-600 - : service.kycLevel === 1 - ? '180, 83, 9' // amber-700 - : service.kycLevel === 2 - ? '220, 38, 38' // red-600 - : service.kycLevel === 3 - ? '185, 28, 28' // red-700 - : service.kycLevel === 4 - ? '153, 27, 27' // red-800 - : '107, 114, 128', // gray-500 fallback - formattedDate: new Date(service.createdAt).toLocaleDateString('en-US', { - year: 'numeric', - month: 'short', - day: 'numeric', - }), - } -}) - -const makeSortUrl = (slug: NonNullable<(typeof filters)['sort']>) => { - const newOrder = filters.sort === slug && filters.order === 'asc' ? 'desc' : 'asc' - const searchParams = new URLSearchParams(Astro.url.search) - searchParams.set('sort', slug) - searchParams.set('order', newOrder) - return `/admin/services?${searchParams.toString()}` -} - -const getPaginationUrl = (pageNum: number) => { - const url = new URL(Astro.url) - url.searchParams.set('page', pageNum.toString()) - return url.toString() -} - -const truncate = (text: string, length: number) => { - if (text.length <= length) return text - return text.substring(0, length) + '...' -} ---- - - -
-

Service Management

-
- {totalServicesCount} services - - - New Service - -
-
- - -
-
-
- -
- -
-
- -
- - -
- -
- - -
- -
- -
- - -
-
-
-
- - -
-
-

Services List

-
- Scroll horizontally to see more → -
-
- -
-
- - - - - - - - - - - - - - { - servicesWithInfo.map((service) => ( - - - - - - - - - - )) - } - -
- - Service - - KYC - - Score - - Status - - - Reqs - - - - - Date - - Actions
-
-
- {service.imageUrl ? ( - {service.name} - ) : ( - {service.name} - )} -
-
-
{service.name}
-
- {truncate(service.description, 45)} -
-
- {service.categories.slice(0, 2).map((category) => ( - - - {category.name} - - ))} - {service.categories.length > 2 && ( - - +{service.categories.length - 2} - - )} -
-
-
-
- - {service.kycLevel} - - -
- = 7, - 'text-yellow-400': service.overallScore >= 4 && service.overallScore < 7, - 'text-red-400': service.overallScore < 4, - })} - > - {service.overallScore} - -
- - {service.privacyScore} - - - {service.trustScore} - -
-
-
- - - - {service.verificationStatus.substring(0, 4)} - -
- - {service.serviceVisibility} - -
-
- - {service._count.verificationRequests} - - {service.formattedDate} - -
-
-
-
- - {/* Pagination controls */} - { - totalPages > 1 && ( -
-
- Showing {services.length > 0 ? skip + 1 : 0} to {Math.min(skip + itemsPerPage, totalServicesCount)}{' '} - of {totalServicesCount} services -
- - -
- ) - } -
- - diff --git a/web/src/pages/admin/services/new.astro b/web/src/pages/admin/services/new.astro deleted file mode 100644 index 0482ad7..0000000 --- a/web/src/pages/admin/services/new.astro +++ /dev/null @@ -1,366 +0,0 @@ ---- -import { AttributeCategory, Currency, VerificationStatus } from '@prisma/client' -import { Icon } from 'astro-icon/components' -import { actions, isInputError } from 'astro:actions' - -import BaseLayout from '../../../layouts/BaseLayout.astro' -import { cn } from '../../../lib/cn' -import { prisma } from '../../../lib/prisma' - -const categories = await Astro.locals.banners.try('Failed to fetch categories', () => - prisma.category.findMany({ - orderBy: { name: 'asc' }, - }) -) - -const attributes = await Astro.locals.banners.try('Failed to fetch attributes', () => - prisma.attribute.findMany({ - orderBy: { category: 'asc' }, - }) -) - -const result = Astro.getActionResult(actions.admin.service.create) -Astro.locals.banners.addIfSuccess(result, 'Service created successfully') -if (result && !result.error) { - return Astro.redirect(`/admin/services/${result.data.service.slug}/edit`) -} -const inputErrors = isInputError(result?.error) ? result.error.fields : {} ---- - - -
-
- service.create -
- -
-
- - - { - inputErrors.name && ( -

{inputErrors.name.join(', ')}

- ) - } -
- -
- - - { - inputErrors.description && ( -

{inputErrors.description.join(', ')}

- ) - } -
- -
- - - { - inputErrors.serviceUrls && ( -

{inputErrors.serviceUrls.join(', ')}

- ) - } -
- -
- - - { - inputErrors.tosUrls && ( -

{inputErrors.tosUrls.join(', ')}

- ) - } -
- -
- - - { - inputErrors.onionUrls && ( -

{inputErrors.onionUrls.join(', ')}

- ) - } -
- -
- -
- -

- Upload a square image for best results. Supported formats: JPG, PNG, WebP, SVG. -

-
- { - inputErrors.imageFile && ( -

{inputErrors.imageFile.join(', ')}

- ) - } -
- -
- -
- { - categories?.map((category) => ( - - )) - } -
- { - inputErrors.categories && ( -

{inputErrors.categories.join(', ')}

- ) - } -
- -
- - - { - inputErrors.kycLevel && ( -

{inputErrors.kycLevel.join(', ')}

- ) - } -
- -
- -
- { - Object.values(AttributeCategory).map((category) => ( -
-

{category}

-
- {attributes - ?.filter((attr) => attr.category === category) - .map((attr) => ( - - ))} -
- {inputErrors.attributes && ( -

{inputErrors.attributes.join(', ')}

- )} -
- )) - } -
-
- -
- - - { - inputErrors.verificationStatus && ( -

{inputErrors.verificationStatus.join(', ')}

- ) - } -
- -
- - - { - inputErrors.verificationSummary && ( -

{inputErrors.verificationSummary.join(', ')}

- ) - } -
- -
- - - { - inputErrors.verificationProofMd && ( -

{inputErrors.verificationProofMd.join(', ')}

- ) - } -
- -
- -
- { - Object.values(Currency).map((currency) => ( - - )) - } -
- { - inputErrors.acceptedCurrencies && ( -

{inputErrors.acceptedCurrencies.join(', ')}

- ) - } -
- -
- - - { - inputErrors.overallScore && ( -

{inputErrors.overallScore.join(', ')}

- ) - } -
- -
- - - { - inputErrors.referral && ( -

{inputErrors.referral.join(', ')}

- ) - } -
- - -
-
-
diff --git a/web/src/pages/admin/users/[username].astro b/web/src/pages/admin/users/[username].astro deleted file mode 100644 index 32a394f..0000000 --- a/web/src/pages/admin/users/[username].astro +++ /dev/null @@ -1,626 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' -import { actions, isInputError } from 'astro:actions' - -import Tooltip from '../../../components/Tooltip.astro' -import BaseLayout from '../../../layouts/BaseLayout.astro' -import { prisma } from '../../../lib/prisma' -import { transformCase } from '../../../lib/strings' -import { timeAgo } from '../../../lib/timeAgo' - -const { username } = Astro.params - -if (!username) return Astro.rewrite('/404') - -const updateResult = Astro.getActionResult(actions.admin.user.update) -Astro.locals.banners.addIfSuccess(updateResult, 'User updated successfully') -if (updateResult && !updateResult.error && username !== updateResult.data.updatedUser.name) { - return Astro.redirect(`/admin/users/${updateResult.data.updatedUser.name}`) -} -const updateInputErrors = isInputError(updateResult?.error) ? updateResult.error.fields : {} - -const addAffiliationResult = Astro.getActionResult(actions.admin.user.serviceAffiliations.add) -Astro.locals.banners.addIfSuccess(addAffiliationResult, 'Service affiliation added successfully') - -const removeAffiliationResult = Astro.getActionResult(actions.admin.user.serviceAffiliations.remove) -Astro.locals.banners.addIfSuccess(removeAffiliationResult, 'Service affiliation removed successfully') - -const [user, allServices] = await Astro.locals.banners.tryMany([ - [ - 'Failed to load user profile', - async () => { - if (!username) return null - - return await prisma.user.findUnique({ - where: { name: username }, - select: { - id: true, - name: true, - displayName: true, - picture: true, - link: true, - admin: true, - verified: true, - verifier: true, - spammer: true, - verifiedLink: true, - internalNotes: { - select: { - id: true, - content: true, - createdAt: true, - addedByUser: { - select: { - id: true, - name: true, - }, - }, - }, - orderBy: { - createdAt: 'desc', - }, - }, - serviceAffiliations: { - select: { - id: true, - role: true, - createdAt: true, - service: { - select: { - id: true, - name: true, - slug: true, - }, - }, - }, - orderBy: { - createdAt: 'desc', - }, - }, - }, - }) - }, - null, - ], - [ - 'Failed to load services', - async () => { - return await prisma.service.findMany({ - select: { - id: true, - name: true, - }, - orderBy: { - name: 'asc', - }, - }) - }, - [], - ], -]) - -if (!user) return Astro.rewrite('/404') ---- - - -
-
-

User Profile: {user.name}

- - - Back to Users - -
- -
-
- { - user.picture ? ( - - ) : ( -
- {user.name.charAt(0) || 'A'} -
- ) - } -
-

{user.name}

-
- { - user.admin && ( - - admin - - ) - } - { - user.verified && ( - - verified - - ) - } - { - user.verifier && ( - - verifier - - ) - } -
-
-
- -
- - -
- - - { - updateInputErrors.name && ( -

{updateInputErrors.name.join(', ')}

- ) - } -
- -
- - - { - Array.isArray(updateInputErrors.displayName) && updateInputErrors.displayName.length > 0 && ( -

{updateInputErrors.displayName.join(', ')}

- ) - } -
- -
- - - { - updateInputErrors.link && ( -

{updateInputErrors.link.join(', ')}

- ) - } -
- -
- - - { - updateInputErrors.picture && ( -

{updateInputErrors.picture.join(', ')}

- ) - } -
- -
- - -

- Upload a square image for best results. Supported formats: JPG, PNG, WebP, AVIF, JXL. Max size: - 5MB. -

-
- -
- - - - - Verified - - - - - -
- - { - updateInputErrors.admin && ( -

{updateInputErrors.admin.join(', ')}

- ) - } - { - updateInputErrors.verifier && ( -

{updateInputErrors.verifier.join(', ')}

- ) - } - { - updateInputErrors.spammer && ( -

{updateInputErrors.spammer.join(', ')}

- ) - } - -
- - - { - updateInputErrors.verifiedLink && ( -

{updateInputErrors.verifiedLink.join(', ')}

- ) - } -
- -
- -
-
- { - Astro.locals.user && user.id !== Astro.locals.user.id && ( - - - Impersonate - - ) - } -
- -
-

Internal Notes

- - { - user.internalNotes.length === 0 ? ( -

No internal notes yet.

- ) : ( -
- {user.internalNotes.map((note) => ( -
-
-
- - {note.addedByUser ? note.addedByUser.name : 'System'} - - - {transformCase(timeAgo.format(note.createdAt, 'twitter-minute-now'), 'sentence')} - -
- -
- - -
- - -
-
-
-
-

{note.content}

-
- -
- ))} -
- ) - } - -
- - - -
-
- -
-

Service Affiliations

- - { - user.serviceAffiliations.length === 0 ? ( -

No service affiliations yet.

- ) : ( -
- {user.serviceAffiliations.map((affiliation) => ( -
-
-
- - {affiliation.service.name} - - - {affiliation.role.toLowerCase()} - -
-
- - {transformCase(timeAgo.format(affiliation.createdAt, 'twitter-minute-now'), 'sentence')} - -
-
- -
- - -
-
- ))} -
- ) - } - -
- - -
-
- - -
- -
- - -
-
- -
- -
-
-
-
-
- - - - diff --git a/web/src/pages/admin/users/index.astro b/web/src/pages/admin/users/index.astro deleted file mode 100644 index ec97374..0000000 --- a/web/src/pages/admin/users/index.astro +++ /dev/null @@ -1,377 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' -import { z } from 'astro:content' -import { orderBy as lodashOrderBy } from 'lodash-es' - -import SortArrowIcon from '../../../components/SortArrowIcon.astro' -import TimeFormatted from '../../../components/TimeFormatted.astro' -import Tooltip from '../../../components/Tooltip.astro' -import BaseLayout from '../../../layouts/BaseLayout.astro' -import { zodParseQueryParamsStoringErrors } from '../../../lib/parseUrlFilters' -import { pluralize } from '../../../lib/pluralize' -import { prisma } from '../../../lib/prisma' -import { formatDateShort } from '../../../lib/timeAgo' - -import type { Prisma } from '@prisma/client' - -const { data: filters } = zodParseQueryParamsStoringErrors( - { - 'sort-by': z.enum(['name', 'role', 'createdAt', 'karma']).default('createdAt'), - 'sort-order': z.enum(['asc', 'desc']).default('desc'), - search: z.string().optional(), - role: z.enum(['user', 'admin', 'verifier', 'verified', 'spammer']).optional(), - }, - Astro -) - -// Set up Prisma orderBy with correct typing -const prismaOrderBy = - filters['sort-by'] === 'name' || filters['sort-by'] === 'createdAt' || filters['sort-by'] === 'karma' - ? { - [filters['sort-by'] === 'karma' ? 'totalKarma' : filters['sort-by']]: - filters['sort-order'] === 'asc' ? 'asc' : 'desc', - } - : { createdAt: 'desc' as const } - -// Build where clause based on role filter -const whereClause: Prisma.UserWhereInput = {} - -if (filters.search) { - whereClause.OR = [{ name: { contains: filters.search, mode: 'insensitive' } }] -} - -if (filters.role) { - switch (filters.role) { - case 'user': { - whereClause.admin = false - whereClause.verifier = false - whereClause.verified = false - whereClause.spammer = false - break - } - case 'admin': { - whereClause.admin = true - break - } - case 'verifier': { - whereClause.verifier = true - break - } - case 'verified': { - whereClause.verified = true - break - } - case 'spammer': { - whereClause.spammer = true - break - } - } -} - -// Retrieve users from the database -const dbUsers = await prisma.user.findMany({ - where: whereClause, - select: { - id: true, - name: true, - verified: true, - admin: true, - verifier: true, - spammer: true, - totalKarma: true, - createdAt: true, - updatedAt: true, - internalNotes: { - select: { - id: true, - content: true, - createdAt: true, - }, - }, - _count: { - select: { - suggestions: true, - comments: true, - }, - }, - }, - orderBy: prismaOrderBy, -}) - -const users = - filters['sort-by'] === 'role' - ? lodashOrderBy(dbUsers, [(u) => (u.admin ? 'admin' : 'user')], [filters['sort-order']]) - : dbUsers - -const makeSortUrl = (slug: NonNullable<(typeof filters)['sort-by']>) => { - const currentSortBy = filters['sort-by'] - const currentSortOrder = filters['sort-order'] - const newSortOrder = currentSortBy === slug && currentSortOrder === 'asc' ? 'desc' : 'asc' - const searchParams = new URLSearchParams(Astro.url.search) - searchParams.set('sort-by', slug) - searchParams.set('sort-order', newSortOrder) - return `/admin/users?${searchParams.toString()}` -} ---- - - -
-

User Management

-
- {users.length} users -
-
- -
-
-
- - -
-
- -
- - -
-
-
-
- -
-
-

Users List

-
- Scroll horizontally to see more → -
-
-
-
- - - - - - - - - - - - - - { - users.map((user) => ( - - - - - - - - - - )) - } - -
- - Name - - - Status - - - Role - - - - Karma - - - - Joined - - - Activity - - Actions -
-
{user.name}
- {user.internalNotes.length > 0 && ( - - `${formatDateShort(note.createdAt, { - prefix: false, - hourPrecision: true, - caseType: 'sentence', - })}: ${note.content}` - ) - .join('\n\n')} - > - - {user.internalNotes.length} internal {pluralize('note', user.internalNotes.length)} - - )} -
-
- {user.spammer && ( - - - Spammer - - )} - {user.verified && ( - - - Verified - - )} - {user.verifier && ( - - - Verifier - - )} -
-
- - {user.admin ? 'Admin' : 'User'} - - - = 100 ? 'text-green-400' : user.totalKarma >= 0 ? 'text-zinc-300' : 'text-red-400'}`} - > - {user.totalKarma} - - - - -
-
- Suggestions - - {user._count.suggestions} - -
-
- Comments - - {user._count.comments} - -
-
-
-
- - - - - - -
-
-
-
-
-
- - diff --git a/web/src/pages/attributes.astro b/web/src/pages/attributes.astro deleted file mode 100644 index 7cc6753..0000000 --- a/web/src/pages/attributes.astro +++ /dev/null @@ -1,403 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' -import { Markdown } from 'astro-remote' -import { Picture } from 'astro:assets' -import { z } from 'astro:content' -import { orderBy } from 'lodash-es' - -import BadgeStandard from '../components/BadgeStandard.astro' -import { makeOverallScoreInfo } from '../components/ScoreSquare.astro' -import SortArrowIcon from '../components/SortArrowIcon.astro' -import { getAttributeCategoryInfo } from '../constants/attributeCategories' -import { getAttributeTypeInfo } from '../constants/attributeTypes' -import { getVerificationStatusInfo } from '../constants/verificationStatus' -import BaseLayout from '../layouts/BaseLayout.astro' -import { sortAttributes } from '../lib/attributes' -import { cn } from '../lib/cn' -import { formatNumber } from '../lib/numbers' -import { zodParseQueryParamsStoringErrors } from '../lib/parseUrlFilters' -import { prisma } from '../lib/prisma' - -const { data: filters } = zodParseQueryParamsStoringErrors( - { - 'sort-by': z.enum(['name', 'category', 'type', 'privacy', 'trust']), - 'sort-order': z.enum(['asc', 'desc']).default('asc'), - }, - Astro -) - -const attributes = await Astro.locals.banners.try( - 'Error fetching attributes', - async () => - prisma.attribute.findMany({ - select: { - id: true, - slug: true, - title: true, - description: true, - category: true, - type: true, - privacyPoints: true, - trustPoints: true, - services: { - select: { - service: { - select: { - id: true, - slug: true, - name: true, - imageUrl: true, - overallScore: true, - verificationStatus: true, - }, - }, - }, - }, - }, - }), - [] -) - -const sortBy = filters['sort-by'] -const sortedAttributes = sortBy - ? orderBy( - sortAttributes(attributes), - sortBy === 'type' - ? (attribute) => getAttributeTypeInfo(attribute.type).order - : sortBy === 'category' - ? (attribute) => getAttributeCategoryInfo(attribute.category).order - : sortBy === 'name' - ? 'title' - : sortBy === 'privacy' - ? 'privacyPoints' - : 'trustPoints', - filters['sort-order'] - ) - : sortAttributes(attributes) - -const attributesWithInfo = sortedAttributes.map((attribute) => ({ - ...attribute, - categoryInfo: getAttributeCategoryInfo(attribute.category), - typeInfo: getAttributeTypeInfo(attribute.type), - services: orderBy( - attribute.services.map(({ service }) => ({ - ...service, - verificationStatusInfo: getVerificationStatusInfo(service.verificationStatus), - overallScoreInfo: makeOverallScoreInfo(service.overallScore), - })), - [ - (service) => (service.verificationStatus === 'VERIFICATION_FAILED' ? 1 : -1), - 'overallScore', - () => Math.random(), - ], - ['asc', 'desc', 'asc'] - ), -})) - -const makeSortUrl = (slug: NonNullable<(typeof filters)['sort-by']>) => { - const sortOrder = filters['sort-by'] === slug ? (filters['sort-order'] === 'asc' ? 'desc' : 'asc') : 'asc' - return `/attributes?sort-by=${slug}&sort-order=${sortOrder}` -} ---- - - -

Service attributes

- -

- Characteristics or features of services that affect their scores. -

-

- - - Learn more about attributes - -

- - -
- { - attributesWithInfo.map((attribute) => ( -
-
-

- {attribute.title} -

-
- - -
-
- -
- -
- -
-
- 0, - 'opacity-50': attribute.privacyPoints === 0, - })} - > - {formatNumber(attribute.privacyPoints, { showSign: true })} - - - Privacy - -
- -
- 0, - 'opacity-50': attribute.trustPoints === 0, - })} - > - {formatNumber(attribute.trustPoints, { showSign: true })} - - - Trust - -
-
- - {attribute.services.length > 0 && ( -
- - Show services - - - -
- )} -
- )) - } -
- - - -
diff --git a/web/src/pages/events.astro b/web/src/pages/events.astro deleted file mode 100644 index 04ccb56..0000000 --- a/web/src/pages/events.astro +++ /dev/null @@ -1,474 +0,0 @@ ---- -import { z } from 'astro/zod' -import { Icon } from 'astro-icon/components' -import { Picture } from 'astro:assets' -import { orderBy } from 'lodash-es' - -import Button from '../components/Button.astro' -import FormatTimeInterval from '../components/FormatTimeInterval.astro' -import TimeFormatted from '../components/TimeFormatted.astro' -import { - eventTypes, - eventTypesZodEnumBySlug, - getEventTypeInfo, - getEventTypeInfoBySlug, -} from '../constants/eventTypes' -import { getVerificationStatusInfo } from '../constants/verificationStatus' -import BaseLayout from '../layouts/BaseLayout.astro' -import { cn } from '../lib/cn' -import { zodParseQueryParamsStoringErrors } from '../lib/parseUrlFilters' -import { prisma } from '../lib/prisma' -import { formatDateShort } from '../lib/timeAgo' -import { createPageUrl } from '../lib/urls' - -import type { Prisma } from '@prisma/client' - -const PAGE_SIZE = 100 - -const { data: params, hasDefaultData: hasDefaultFilters } = zodParseQueryParamsStoringErrors( - { - page: z.coerce.number().int().min(1).default(1), - now: z.coerce.date().default(new Date()), - from: z.preprocess((val) => (val === '' ? undefined : val), z.coerce.date().optional()), - to: z.preprocess((val) => (val === '' ? undefined : val), z.coerce.date().optional()), - /** Service's slug */ - service: z.string().optional(), - type: eventTypesZodEnumBySlug.optional(), - }, - Astro -) - -const [services, [dbEvents, totalEvents]] = await Astro.locals.banners.tryMany([ - [ - 'Error fetching services', - async () => - prisma.service.findMany({ - where: { - events: { - some: { - visible: true, - }, - }, - }, - select: { - id: true, - slug: true, - name: true, - imageUrl: true, - verificationStatus: true, - }, - orderBy: { - name: 'asc', - }, - }), - [], - ], - [ - 'Error fetching events', - async () => - prisma.event.findManyAndCount({ - where: { - visible: true, - createdAt: { - lte: params.now, - }, - ...(params.service ? { service: { slug: params.service } } : {}), - ...(params.type ? { type: getEventTypeInfoBySlug(params.type).id } : {}), - ...(params.from || params.to - ? { - OR: [ - ...(params.from - ? ([ - { endedAt: null }, - { endedAt: { gte: params.from } }, - ] satisfies Prisma.EventWhereInput[]) - : []), - ...(params.to - ? ([{ startedAt: { lte: params.to } }] satisfies Prisma.EventWhereInput[]) - : []), - ], - } - : {}), - }, - select: { - id: true, - title: true, - content: true, - source: true, - type: true, - startedAt: true, - endedAt: true, - service: { - select: { - id: true, - slug: true, - name: true, - imageUrl: true, - verificationStatus: true, - }, - }, - }, - orderBy: { - startedAt: 'desc', - }, - skip: (params.page - 1) * PAGE_SIZE, - take: PAGE_SIZE, - }), - [[], 0] as const, - ], -]) - -const events = orderBy( - dbEvents.map((event) => ({ - ...event, - actualEndedAt: event.endedAt ?? params.now, - typeInfo: getEventTypeInfo(event.type), - service: { - ...event.service, - verificationStatusInfo: getVerificationStatusInfo(event.service.verificationStatus), - }, - })), - ['actualEndedAt', 'startedAt'], - 'desc' -) -const totalPages = Math.ceil(totalEvents / PAGE_SIZE) || 1 -const hasMorePages = params.page < totalPages - -const createUrlWithoutFilter = (paramName: keyof typeof params) => { - const url = new URL(Astro.url) - url.searchParams.delete(paramName) - url.searchParams.forEach((value, key) => { - if (value === '') { - url.searchParams.delete(key) - } - }) - return url.toString() -} ---- - - -

- Service Events Timeline -

-
-
-

- FILTERS -

- - - Clear all - -
- - - - - - - - - - - - - -
- - - Page {params.page} of {totalPages} - -
- - -
- - )} - - ) - } - - { - !!notificationPreferences && ( -
-

- - Notification Settings -

- -
- {notificationPreferenceFields.map((field) => ( - - ))} - -
-
-
-
- ) - } - -
diff --git a/web/src/pages/ogimage.png.ts b/web/src/pages/ogimage.png.ts deleted file mode 100644 index 211baa9..0000000 --- a/web/src/pages/ogimage.png.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ogImageTemplates } from '../components/OgImage' -import { urlParamsToObject } from '../lib/urls' - -import type { APIRoute } from 'astro' - -export const GET: APIRoute = ({ url }) => { - const { template, ...props } = urlParamsToObject(url.searchParams) - - if (!template) return ogImageTemplates.default() - - if (!(template in ogImageTemplates)) { - console.error(`Invalid template: "${template}"`) - return ogImageTemplates.default() - } - - const response = ogImageTemplates[template as keyof typeof ogImageTemplates](props) - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (!response) { - console.error(`Cannot generate image for template: ${template} and props: ${JSON.stringify(props)}`) - return ogImageTemplates.default() - } - - return response -} diff --git a/web/src/pages/service-suggestion/[id].astro b/web/src/pages/service-suggestion/[id].astro deleted file mode 100644 index a18239e..0000000 --- a/web/src/pages/service-suggestion/[id].astro +++ /dev/null @@ -1,163 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' -import { actions } from 'astro:actions' - -import AdminOnly from '../../components/AdminOnly.astro' -import Chat from '../../components/Chat.astro' -import ServiceCard from '../../components/ServiceCard.astro' -import { getServiceSuggestionStatusInfo } from '../../constants/serviceSuggestionStatus' -import BaseLayout from '../../layouts/BaseLayout.astro' -import { cn } from '../../lib/cn' -import { parseIntWithFallback } from '../../lib/numbers' -import { prisma } from '../../lib/prisma' -import { makeLoginUrl } from '../../lib/redirectUrls' -import { formatDateShort } from '../../lib/timeAgo' - -const user = Astro.locals.user -if (!user) { - return Astro.redirect(makeLoginUrl(Astro.url, { message: 'Login to view service suggestion' })) -} - -const { id: serviceSuggestionIdRaw } = Astro.params -const serviceSuggestionId = parseIntWithFallback(serviceSuggestionIdRaw) -if (!serviceSuggestionId) { - return Astro.rewrite('/404') -} - -const serviceSuggestion = await Astro.locals.banners.try('Error fetching service suggestion', async () => - prisma.serviceSuggestion.findUnique({ - select: { - id: true, - status: true, - notes: true, - createdAt: true, - service: { - select: { - id: true, - name: true, - slug: true, - description: true, - overallScore: true, - kycLevel: true, - imageUrl: true, - verificationStatus: true, - acceptedCurrencies: true, - categories: { - select: { - name: true, - icon: true, - }, - }, - }, - }, - messages: { - select: { - id: true, - content: true, - createdAt: true, - user: { - select: { - id: true, - name: true, - picture: true, - }, - }, - }, - orderBy: { - createdAt: 'desc', - }, - }, - }, - where: { - id: serviceSuggestionId, - userId: user.id, - }, - }) -) - -if (!serviceSuggestion) { - if (user.admin) return Astro.redirect(`/admin/service-suggestions/${serviceSuggestionIdRaw}`) - return Astro.rewrite('/404') -} - -const statusInfo = getServiceSuggestionStatusInfo(serviceSuggestion.status) ---- - - -

Edit service

- - - - - View in admin - - - - - -
-
-
- Status: - - - {statusInfo.label} - -
- -
- Submitted: - - { - formatDateShort(serviceSuggestion.createdAt, { - prefix: false, - hourPrecision: true, - caseType: 'sentence', - }) - } - -
-
- -
-
Notes for moderators:
-
- {serviceSuggestion.notes ?? Empty} -
-
-
- - -
diff --git a/web/src/pages/service-suggestion/edit.astro b/web/src/pages/service-suggestion/edit.astro deleted file mode 100644 index 8eda41d..0000000 --- a/web/src/pages/service-suggestion/edit.astro +++ /dev/null @@ -1,102 +0,0 @@ ---- -import { actions, isInputError } from 'astro:actions' -import { z } from 'astro:content' - -import Captcha from '../../components/Captcha.astro' -import InputHoneypotTrap from '../../components/InputHoneypotTrap.astro' -import InputSubmitButton from '../../components/InputSubmitButton.astro' -import InputTextArea from '../../components/InputTextArea.astro' -import ServiceCard from '../../components/ServiceCard.astro' -import BaseLayout from '../../layouts/BaseLayout.astro' -import { zodParseQueryParamsStoringErrors } from '../../lib/parseUrlFilters' -import { prisma } from '../../lib/prisma' -import { makeLoginUrl } from '../../lib/redirectUrls' - -const user = Astro.locals.user -if (!user) { - return Astro.redirect(makeLoginUrl(Astro.url, { message: 'Login to suggest a new service' })) -} - -const result = Astro.getActionResult(actions.serviceSuggestion.editService) -if (result && !result.error) { - return Astro.redirect(`/service-suggestion/${result.data.serviceSuggestion.id}`) -} -const inputErrors = isInputError(result?.error) ? result.error.fields : {} - -const { data: params } = zodParseQueryParamsStoringErrors( - { - serviceId: z.coerce.number().int().positive(), - notes: z.string().default(''), - }, - Astro -) - -if (!params.serviceId) return Astro.rewrite('/404') - -const service = await Astro.locals.banners.try( - 'Failed to fetch service', - async () => - prisma.service.findUnique({ - select: { - id: true, - name: true, - slug: true, - description: true, - overallScore: true, - kycLevel: true, - imageUrl: true, - verificationStatus: true, - acceptedCurrencies: true, - categories: { - select: { - name: true, - icon: true, - }, - }, - }, - where: { id: params.serviceId }, - }), - null -) - -if (!service) return Astro.rewrite('/404') ---- - - -

Edit service

- - - -
- - - - - - - - - - -
diff --git a/web/src/pages/service-suggestion/index.astro b/web/src/pages/service-suggestion/index.astro deleted file mode 100644 index 464cfa1..0000000 --- a/web/src/pages/service-suggestion/index.astro +++ /dev/null @@ -1,174 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' -import { actions } from 'astro:actions' -import { Picture } from 'astro:assets' -import { z } from 'astro:content' - -import defaultServiceImage from '../../assets/fallback-service-image.jpg' -import Button from '../../components/Button.astro' -import TimeFormatted from '../../components/TimeFormatted.astro' -import Tooltip from '../../components/Tooltip.astro' -import { - getServiceSuggestionStatusInfo, - serviceSuggestionStatuses, -} from '../../constants/serviceSuggestionStatus' -import { getServiceSuggestionTypeInfo } from '../../constants/serviceSuggestionType' -import BaseLayout from '../../layouts/BaseLayout.astro' -import { zodEnumFromConstant } from '../../lib/arrays' -import { cn } from '../../lib/cn' -import { zodParseQueryParamsStoringErrors } from '../../lib/parseUrlFilters' -import { prisma } from '../../lib/prisma' -import { makeLoginUrl } from '../../lib/redirectUrls' - -const user = Astro.locals.user -if (!user) { - return Astro.redirect(makeLoginUrl(Astro.url, { message: 'Login to manage service suggestions' })) -} - -const { data: filters } = zodParseQueryParamsStoringErrors( - { - serviceId: z.array(z.number().int().positive()).default([]), - status: z.array(zodEnumFromConstant(serviceSuggestionStatuses, 'value')).default([]), - }, - Astro -) - -const serviceSuggestions = await Astro.locals.banners.try('Error fetching service suggestions', async () => - prisma.serviceSuggestion.findMany({ - select: { - id: true, - type: true, - status: true, - createdAt: true, - service: { - select: { - id: true, - name: true, - slug: true, - imageUrl: true, - verificationStatus: true, - }, - }, - }, - where: { - id: filters.serviceId.length > 0 ? { in: filters.serviceId } : undefined, - status: filters.status.length > 0 ? { in: filters.status } : undefined, - userId: user.id, - }, - orderBy: { - createdAt: 'desc', - }, - }) -) - -if (!serviceSuggestions) { - return Astro.rewrite('/404') -} - -const createResult = Astro.getActionResult(actions.serviceSuggestion.createService) -const success = !!createResult && !createResult.error ---- - - -
-

Service suggestions

-
- - { - success && ( -
- - Service suggestion submitted successfully! -
- ) - } - - { - serviceSuggestions.length === 0 ? ( -

No suggestions yet.

- ) : ( -
-
-

Service

-

Type

-

Status

-

Created

-

Actions

- - {serviceSuggestions.map((suggestion) => { - const typeInfo = getServiceSuggestionTypeInfo(suggestion.type) - const statusInfo = getServiceSuggestionStatusInfo(suggestion.status) - - return ( - <> - - - {suggestion.service.name} - - - - - - - - - - - - - - -
-
- ) - } -
diff --git a/web/src/pages/service-suggestion/new.astro b/web/src/pages/service-suggestion/new.astro deleted file mode 100644 index a987c96..0000000 --- a/web/src/pages/service-suggestion/new.astro +++ /dev/null @@ -1,308 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' -import { actions, isInputError } from 'astro:actions' - -import { - SUGGESTION_DESCRIPTION_MAX_LENGTH, - SUGGESTION_NAME_MAX_LENGTH, - SUGGESTION_NOTES_MAX_LENGTH, - SUGGESTION_SLUG_MAX_LENGTH, -} from '../../actions/serviceSuggestion' -import Captcha from '../../components/Captcha.astro' -import InputCardGroup from '../../components/InputCardGroup.astro' -import InputCheckboxGroup from '../../components/InputCheckboxGroup.astro' -import InputHoneypotTrap from '../../components/InputHoneypotTrap.astro' -import InputImageFile from '../../components/InputImageFile.astro' -import InputSubmitButton from '../../components/InputSubmitButton.astro' -import InputText from '../../components/InputText.astro' -import InputTextArea from '../../components/InputTextArea.astro' -import { currencies } from '../../constants/currencies' -import { kycLevels } from '../../constants/kycLevels' -import BaseLayout from '../../layouts/BaseLayout.astro' -import { prisma } from '../../lib/prisma' -import { makeLoginUrl } from '../../lib/redirectUrls' - -const user = Astro.locals.user -if (!user) { - return Astro.redirect(makeLoginUrl(Astro.url, { message: 'Login to suggest a new service' })) -} - -const result = Astro.getActionResult(actions.serviceSuggestion.createService) -if (result && !result.error && !result.data.hasDuplicates) { - return Astro.redirect(`/service-suggestion/${result.data.serviceSuggestion.id}`) -} -const inputErrors = isInputError(result?.error) ? result.error.fields : {} - -const [categories, attributes] = await Astro.locals.banners.tryMany([ - [ - 'Failed to fetch categories', - () => - prisma.category.findMany({ - orderBy: { name: 'asc' }, - select: { - id: true, - name: true, - icon: true, - }, - }), - [], - ], - [ - 'Failed to fetch attributes', - () => - prisma.attribute.findMany({ - orderBy: { category: 'asc' }, - select: { - id: true, - title: true, - }, - }), - [], - ], -]) ---- - - -

Service suggestion

- -
- { - result?.data?.hasDuplicates && ( - <> -
- - -

- Possible duplicates found -

- -

Is your service already listed below?

- -
- {result.data.possibleDuplicates.map((duplicate) => { - const editServiceUrl = new URL('/service-suggestion/edit', Astro.url) - editServiceUrl.searchParams.set('serviceId', duplicate.id.toString()) - if (result.data.extraNotes) { - editServiceUrl.searchParams.set('notes', result.data.extraNotes) - } - - return ( -
-
-

{duplicate.name}

-

{duplicate.description}

-
- -
- ) - })} -
- - -

- Review your suggestion -

- - ) - } - - - - - - - - - - - - - ({ - label: kycLevel.name, - value: kycLevel.id.toString(), - icon: kycLevel.icon, - description: `${kycLevel.description}\n\n_KYC Level ${kycLevel.value}/4_`, - }))} - iconSize="md" - cardSize="md" - required - error={inputErrors.kycLevel} - /> - - ({ - label: category.name, - value: category.id.toString(), - icon: category.icon, - }))} - error={inputErrors.categories} - /> - - ({ - label: attribute.title, - value: attribute.id.toString(), - }))} - error={inputErrors.attributes} - /> - - ({ - label: currency.name, - value: currency.id, - icon: currency.icon, - }))} - error={inputErrors.acceptedCurrencies} - required - multiple - /> - - - - - - - - - - - - - - diff --git a/web/src/pages/service/[slug].astro b/web/src/pages/service/[slug].astro deleted file mode 100644 index f6236fd..0000000 --- a/web/src/pages/service/[slug].astro +++ /dev/null @@ -1,1506 +0,0 @@ ---- -import { VerificationStepStatus } from '@prisma/client' -import { Icon } from 'astro-icon/components' -import { Markdown } from 'astro-remote' -import { Schema } from 'astro-seo-schema' -import { actions } from 'astro:actions' -import { Picture } from 'astro:assets' -import { head, orderBy, shuffle, sortBy, tail } from 'lodash-es' - -import AdminOnly from '../../components/AdminOnly.astro' -import BadgeSmall from '../../components/BadgeSmall.astro' -import BadgeStandard from '../../components/BadgeStandard.astro' -import Button from '../../components/Button.astro' -import CommentSection from '../../components/CommentSection.astro' -import CommentSummary from '../../components/CommentSummary.astro' -import DropdownButton from '../../components/DropdownButton.astro' -import DropdownButtonItemForm from '../../components/DropdownButtonItemForm.astro' -import DropdownButtonItemLink from '../../components/DropdownButtonItemLink.astro' -import FormatTimeInterval from '../../components/FormatTimeInterval.astro' -import { makeOgImageUrl, type OgImageAllTemplatesWithProps } from '../../components/OgImage' -import ScoreGauge from '../../components/ScoreGauge.astro' -import ScoreSquare from '../../components/ScoreSquare.astro' -import ServiceLinkButton from '../../components/ServiceLinkButton.astro' -import TimeFormatted from '../../components/TimeFormatted.astro' -import Tooltip from '../../components/Tooltip.astro' -import VerificationWarningBanner from '../../components/VerificationWarningBanner.astro' -import { getAttributeCategoryInfo } from '../../constants/attributeCategories' -import { getAttributeTypeInfo } from '../../constants/attributeTypes' -import { currencies, getCurrencyInfo } from '../../constants/currencies' -import { getEventTypeInfo } from '../../constants/eventTypes' -import { getKycLevelInfo, kycLevels } from '../../constants/kycLevels' -import { serviceVisibilitiesById } from '../../constants/serviceVisibility' -import { getTosHighlightRatingInfo } from '../../constants/tosHighlightRating' -import { getUserSentimentInfo } from '../../constants/userSentiment' -import { getVerificationStatusInfo, verificationStatusesByValue } from '../../constants/verificationStatus' -import BaseLayout from '../../layouts/BaseLayout.astro' -import { someButNotAll, undefinedIfEmpty } from '../../lib/arrays' -import { makeNonDbAttributes, sortAttributes } from '../../lib/attributes' -import { cn } from '../../lib/cn' -import { formatContactMethod } from '../../lib/contactMethods' -import { getOrCreateNotificationPreferences } from '../../lib/notificationPreferences' -import { formatNumber, type FormatNumberOptions } from '../../lib/numbers' -import { pluralize } from '../../lib/pluralize' -import { prisma } from '../../lib/prisma' -import { makeLoginUrl } from '../../lib/redirectUrls' -import { KYCNOTME_SCHEMA_MINI } from '../../lib/schema' -import { transformCase } from '../../lib/strings' -import { urlDomain } from '../../lib/urls' - -import type { BreadcrumArray } from '../../components/BaseHead.astro' -import type { Prisma } from '@prisma/client' -import type { ContactPoint, ListItem, Organization, Review, WebPage, WithContext } from 'schema-dts' - -const { slug } = Astro.params - -const user = Astro.locals.user - -const result = Astro.getActionResult(actions.service.requestVerification) - -const now = new Date() - -const [service, dbNotificationPreferences] = await Astro.locals.banners.tryMany([ - [ - 'Error fetching service', - async () => - prisma.service.findUnique({ - where: { slug }, - select: { - id: true, - slug: true, - name: true, - description: true, - kycLevel: true, - overallScore: true, - privacyScore: true, - trustScore: true, - verificationStatus: true, - serviceVisibility: true, - verificationSummary: true, - verificationProofMd: true, - tosUrls: true, - serviceUrls: true, - onionUrls: true, - i2pUrls: true, - referral: true, - imageUrl: true, - listedAt: true, - createdAt: true, - acceptedCurrencies: true, - tosReview: true, - tosReviewAt: true, - userSentiment: true, - userSentimentAt: true, - averageUserRating: true, - isRecentlyListed: true, - contactMethods: { - select: { - iconId: true, - value: true, - }, - }, - attributes: { - select: { - attribute: { - select: { - id: true, - type: true, - category: true, - title: true, - description: true, - privacyPoints: true, - trustPoints: true, - }, - }, - }, - }, - categories: { - select: { - icon: true, - name: true, - slug: true, - }, - }, - events: { - where: { - visible: true, - }, - select: { - title: true, - content: true, - type: true, - startedAt: true, - endedAt: true, - source: true, - }, - }, - suggestions: { - select: { - id: true, - status: true, - }, - where: { - userId: user?.id, - }, - }, - verificationRequests: { - select: { - id: true, - userId: true, - }, - }, - verificationSteps: { - select: { - title: true, - description: true, - status: true, - evidenceMd: true, - createdAt: true, - updatedAt: true, - }, - }, - _count: { - select: { - comments: { - where: { - ratingActive: true, - status: { - in: ['APPROVED', 'VERIFIED'], - }, - parentId: null, - suspicious: false, - }, - }, - }, - }, - }, - }), - null, - ], - [ - 'Error while fetching notification preferences', - () => - user - ? getOrCreateNotificationPreferences(user.id, { - id: true, - onRootCommentCreatedForServices: { - where: { slug }, - select: { id: true }, - }, - onEventCreatedForServices: { - where: { slug }, - select: { id: true }, - }, - onVerificationChangeForServices: { - where: { slug }, - select: { id: true }, - }, - }) - : null, - null, - ], -]) - -const makeWatchingDetails = ( - dbNotificationPreferences: Prisma.NotificationPreferencesGetPayload<{ - select: { - onRootCommentCreatedForServices: { - select: { id: true } - } - onEventCreatedForServices: { - select: { id: true } - } - onVerificationChangeForServices: { - select: { id: true } - } - } - }> | null, - serviceId: number | undefined -) => { - if (!dbNotificationPreferences || !serviceId) return null - - const comments = dbNotificationPreferences.onRootCommentCreatedForServices.some( - ({ id }) => id === serviceId - ) - const events = dbNotificationPreferences.onEventCreatedForServices.some(({ id }) => id === serviceId) - const verification = dbNotificationPreferences.onVerificationChangeForServices.some( - ({ id }) => id === serviceId - ) - const all = comments && events && verification - - return { - comments, - events, - verification, - all: all ? true : someButNotAll(comments, events, verification) ? null : false, - } as const -} - -const watchingDetails = makeWatchingDetails(dbNotificationPreferences, service?.id) - -if (!service) return Astro.rewrite('/404') - -if (service.serviceVisibility !== 'PUBLIC' && service.serviceVisibility !== 'UNLISTED') { - return Astro.rewrite('/404') -} - -const statusIcon = { - ...verificationStatusesByValue, - APPROVED: undefined, -}[service.verificationStatus] - -const shuffledLinks = { - clearnet: shuffle(service.serviceUrls), - onion: shuffle(service.onionUrls), - i2p: shuffle(service.i2pUrls), -} -const shownLinks = [head(shuffledLinks.clearnet), head(shuffledLinks.onion), head(shuffledLinks.i2p)].filter( - (url) => url !== undefined -) -const hiddenLinks = [ - ...tail(shuffledLinks.clearnet), - ...tail(shuffledLinks.onion), - ...tail(shuffledLinks.i2p), -] - -const kycLevelInfo = getKycLevelInfo(`${service.kycLevel}`) -const userSentiment = service.userSentiment - ? { ...service.userSentiment, info: getUserSentimentInfo(service.userSentiment.sentiment) } - : null - -const attributes = sortAttributes([ - ...makeNonDbAttributes(service, { filter: true }), - ...service.attributes.map(({ attribute }) => ({ - ...attribute, - links: - attribute.type === 'GOOD' || attribute.type === 'INFO' - ? [ - { - url: `/?attr-${attribute.id}=yes`, - label: 'Search with this', - icon: 'ri:search-line', - }, - ] - : [ - { - url: `/?attr-${attribute.id}=yes`, - label: 'With this', - icon: 'ri:search-line', - }, - { - url: `/?attr-${attribute.id}=no`, - label: 'Without this', - icon: 'ri:search-line', - }, - ], - })), -]).map((attribute) => ({ - ...attribute, - categoryInfo: getAttributeCategoryInfo(attribute.category), - typeInfo: getAttributeTypeInfo(attribute.type), -})) - -const statusInfo = getVerificationStatusInfo(service.verificationStatus) - -const hasRequestedVerification = - !!user && service.verificationRequests.some((request) => request.userId === user.id) - -const VerificationRequestElement = user ? 'button' : 'a' - -const getVerificationStepStatusInfo = (status: VerificationStepStatus) => { - switch (status) { - case VerificationStepStatus.PENDING: - return { - text: 'Pending', - icon: 'ri:loader-2-line', - color: 'gray', - timelineIconClass: 'text-gray-400', - } as const - case VerificationStepStatus.IN_PROGRESS: - return { - text: 'In Progress', - icon: 'ri:loader-4-line', - color: 'blue', - timelineIconClass: 'text-blue-400 animate-spin', - } as const - case VerificationStepStatus.PASSED: - return { - text: 'Passed', - icon: 'ri:check-line', - color: 'green', - timelineIconClass: 'text-green-400', - } as const - case VerificationStepStatus.FAILED: - return { - text: 'Failed', - icon: 'ri:close-line', - color: 'red', - timelineIconClass: 'text-red-400', - } as const - default: - return { - text: 'Unknown', - icon: 'ri:question-mark', - color: 'gray', - timelineIconClass: 'text-gray-500', - } as const - } -} -const itemReviewedId = new URL(`/service/${service.slug}`, Astro.url).href - -const ogImageTemplateData = { - template: 'generic', - title: service.name, -} satisfies OgImageAllTemplatesWithProps ---- - - ({ - ...method, - ...(formatContactMethod(method.value) ?? { type: 'unknown', formattedValue: method.value }), - })) - .map(({ type, formattedValue }) => { - switch (type) { - case 'telephone': { - return { - '@type': 'ContactPoint', - telephone: formattedValue, - } - } - case 'email': { - return { - '@type': 'ContactPoint', - email: formattedValue, - } - } - default: { - return null - } - } - }) - .filter((value) => value !== null) - ), - } satisfies WithContext, - - { - '@context': 'https://schema.org', - '@type': 'WebPage', - name: service.name, - url: Astro.url.href, - mainEntity: { - '@id': itemReviewedId, - }, - datePublished: service.listedAt?.toISOString(), - dateCreated: service.createdAt.toISOString(), - image: makeOgImageUrl(ogImageTemplateData, Astro.url), - } satisfies WithContext, - ]} - breadcrumbs={[ - ...service.categories.map( - (category) => - [ - { - name: 'Services', - url: '/', - }, - { - name: category.name, - url: `/service/?categories=${category.slug}`, - }, - { - name: service.name, - url: `/service/${service.slug}`, - }, - ] satisfies BreadcrumArray - ), - ...service.acceptedCurrencies.map((currency) => { - const currencyInfo = getCurrencyInfo(currency) - return [ - { - name: 'Services', - url: '/', - }, - { - name: currencyInfo.name, - url: `/service/?currencies=${currencyInfo.slug}`, - }, - { - name: service.name, - url: `/service/${service.slug}`, - }, - ] satisfies BreadcrumArray - }), - ]} -> - { - service.serviceVisibility === 'UNLISTED' && ( -
- - Unlisted service, only accessible via direct link and won't appear in searches. -
- ) - } - - - { - service.verificationSteps.some((step) => step.status === VerificationStepStatus.FAILED) && ( -
- - - This service has failed one or more verification steps. Review the verification details carefully. - -
- ) - } - -
- { - !!service.imageUrl && ( - - ) - } -

- {service.name}{ - !!statusIcon && ( - - - - ) - } -

- -
- { - user ? ( - - - {!!watchingDetails && ( - - - - )} - - - - {!!watchingDetails && ( - - - - )} - - - - {!!watchingDetails && ( - - - - )} - - - - {!!watchingDetails && ( - - - - )} - - - ) : ( - - - - - ) - } - - -
-
- -
= 3, - })} - > -
    = 3, - })} - aria-label="Categories" - > - { - service.categories.map((category) => ( - - )) - } -
- -
- { - currencies.map((currency) => { - const isAccepted = service.acceptedCurrencies.includes(currency.id) - - return ( - - - - ) - }) - } -
-
- -
- -
- - { - shownLinks.length + hiddenLinks.length > 0 && ( -
    - {shownLinks.map((url) => ( -
  • - 0} - /> -
  • - ))} - - - - {hiddenLinks.length > 0 && ( -
  • - -
  • - )} - - {hiddenLinks.map((url) => ( - - ))} -
- ) - } - - { - service.contactMethods.length > 0 && ( - - ) - } - -

- Scores -

-
-
- - -
- - - - -
-
- } - /> -
- -
- lvl. - {kycLevelInfo.value}/{kycLevels.length - 1} -
-
- -
-
{kycLevelInfo.name}
-
- {kycLevelInfo.description} -
-
-
-
- - { - attributes.length > 0 && ( -
    - {attributes.map((attribute) => { - const baseFormatOptions = { - roundDigits: 2, - showSign: true, - removeTrailingZeros: true, - } as const satisfies FormatNumberOptions - const weights = [ - { - type: 'privacy', - label: 'Privacy', - value: attribute.privacyPoints, - formatOptions: baseFormatOptions, - }, - { - type: 'trust', - label: 'Trust', - value: attribute.trustPoints, - formatOptions: baseFormatOptions, - }, - ] as const satisfies { - type: string - label: string - value: number - formatOptions: FormatNumberOptions - }[] - - return ( -
  • -
    - - - {attribute.title} - - - -
    -
    - -
    - -
    - {weights.map((w) => ( -
    - 0, - 'opacity-50': w.value === 0, - })} - > - {formatNumber(w.value, w.formatOptions)} - - - {w.label} - -
    - ))} -
    - -
    - {attribute.links.map((link) => ( - - - {link.label} - - ))} -
    -
    -
    -
  • - ) - })} -
- ) - } - - - -

- Terms of Service Review -

- { - service.verificationStatus === 'VERIFICATION_SUCCESS' || service.verificationStatus === 'APPROVED' ? ( - service.tosReview ? ( - <> - h.rating === 'positive') - .map( - (h, i) => - ({ - '@type': 'ListItem', - position: i + 1, - name: h.title, - description: h.content, - }) satisfies ListItem - ), - negativeNotes: service.tosReview.highlights - .filter((h) => h.rating === 'negative') - .map( - (h, i) => - ({ - '@type': 'ListItem', - position: i + 1, - name: h.title, - description: h.content, - }) satisfies ListItem - ), - } satisfies WithContext - } - /> - -
-
- {service.tosReview.summary ? ( - - ) : ( -

No summary available

- )} -
-
- -
- {sortBy( - service.tosReview.highlights.map((highlight) => ({ - ...highlight, - ratingInfo: getTosHighlightRatingInfo(highlight.rating), - })), - 'ratingInfo.order' - ).map((highlight) => ( -
-

- - {highlight.title} -

- {!!highlight.content && ( -
- -
- )} -
- ))} -
- -

- {!!service.tosReviewAt && ( - <> - Reviewed - - )} - {!!service.tosReviewAt && !!service.tosUrls.length && 'from'} - {service.tosUrls.map((url) => ( - - {urlDomain(url)} - - ))} -

-

- ToS reviews are AI-generated and should be used as a reference only. -

- - ) : ( -
-

Not reviewed yet

- {service.tosUrls.length > 0 && ( -

- {service.tosUrls.map((url) => ( - - {urlDomain(url)} - - ))} -

- )} -
- ) - ) : ( -
-

Not available on {transformCase(statusInfo.label, 'lower')} services

- {service.tosUrls.length > 0 && ( -

- {service.tosUrls.map((url) => ( - - {urlDomain(url)} - - ))} -

- )} -
- ) - } - -
-

Events

- - View all - - -
- { - service.events.length > 0 ? ( -
    - {orderBy( - service.events.map((event) => ({ - ...event, - actualEndedAt: event.endedAt ?? now, - })), - ['actualEndedAt', 'startedAt'], - 'desc' - ).map((event) => { - const typeInfo = getEventTypeInfo(event.type) - - return ( -
  1. -
    -
    - -
    - -
    -
    - -
    -

    - {event.title} -

    - -

    {event.content}

    - {event.source && ( - - Source - - )} -
    -
  2. - ) - })} -
  3. -
- ) : ( -

No events reported

- ) - } - - - -

- Verification -

-
-
-
-
-
- - {statusInfo.label} -
- -

- {statusInfo.description} -

-
- - { - !!service.verificationSummary && ( -
- -
- ) - } - - { - service.verificationSteps.length > 0 && ( -
-

- - Verification Steps -

-
    - {service.verificationSteps.map((step) => { - const statusInfo = getVerificationStepStatusInfo(step.status) - return ( -
  • -
    - -
    -
    - {step.title} - - - {statusInfo.text} - - -
    -
    -
    -

    - Added - -

    - {step.description && ( -
    {step.description}
    - )} - {step.evidenceMd && ( -
    -
    Evidence:
    -
    - -
    -
    - )} -
    -
    -
  • - ) - })} -
-
- ) - } -
- -
- { - service.verificationStatus !== 'VERIFICATION_SUCCESS' && - service.verificationStatus !== 'VERIFICATION_FAILED' && ( -
- - - { - const url = new URL(Astro.url) - url.hash = '#request-verification-form' - return url - })() - ) - : undefined - } - class="border-night-400 bg-night-800 hover:bg-night-900 flex w-full items-center gap-2 rounded-lg border px-4 py-2 text-sm shadow-sm transition-colors duration-200" - > - - - -

- {service.verificationRequests.length.toLocaleString()}{' '} - {pluralize('request', service.verificationRequests.length)} -

- {result?.error &&

{result.error.message}

} -
- ) - } - - { - service.verificationProofMd && ( -
-
- - { - service.verificationProofMd && ( - <> - - - - ) - } -
- -

- Comments -

-
-
-

About comments

-
    -
  • Comments are visible after approval.
  • -
  • Moderation is light.
  • -
  • Double-check before trusting.
  • -
-
- -
-
- - - { - userSentiment && service.averageUserRating !== null && ( - - ({ - '@type': 'ListItem', - position: index + 1, - name: item, - }) satisfies ListItem - ), - negativeNotes: userSentiment.whatUsersDislike.map( - (item, index) => - ({ - '@type': 'ListItem', - position: index + 1, - name: item, - }) satisfies ListItem - ), - } satisfies WithContext - } - /> - ) - } - -
-
-

- AI Summary -

- { - service.userSentimentAt && ( -
- Updated - -
- ) - } -
- - { - userSentiment ? ( - <> -
- -
- -
-
    - {userSentiment.whatUsersLike.map((item) => ( -
  • - {item} -
  • - ))} -
- -
    - {userSentiment.whatUsersDislike.map((item) => ( -
  • - {item} -
  • - ))} -
-
- - ) : ( -

- Not reviewed yet -

- ) - } -
-
- -
- - diff --git a/web/src/pages/u/[username].astro b/web/src/pages/u/[username].astro deleted file mode 100644 index ad01bc7..0000000 --- a/web/src/pages/u/[username].astro +++ /dev/null @@ -1,912 +0,0 @@ ---- -import { Icon } from 'astro-icon/components' -import { Picture } from 'astro:assets' -import { sortBy } from 'lodash-es' - -import defaultServiceImage from '../../assets/fallback-service-image.jpg' -import AdminOnly from '../../components/AdminOnly.astro' -import BadgeSmall from '../../components/BadgeSmall.astro' -import TimeFormatted from '../../components/TimeFormatted.astro' -import Tooltip from '../../components/Tooltip.astro' -import { karmaUnlocks } from '../../constants/karmaUnlocks' -import { SUPPORT_EMAIL } from '../../constants/project' -import { getServiceSuggestionStatusInfo } from '../../constants/serviceSuggestionStatus' -import { getServiceSuggestionTypeInfo } from '../../constants/serviceSuggestionType' -import { getServiceUserRoleInfo } from '../../constants/serviceUserRoles' -import { verificationStatusesByValue } from '../../constants/verificationStatus' -import BaseLayout from '../../layouts/BaseLayout.astro' -import { cn } from '../../lib/cn' -import { makeUserWithKarmaUnlocks } from '../../lib/karmaUnlocks' -import { prisma } from '../../lib/prisma' -import { KYCNOTME_SCHEMA_MINI } from '../../lib/schema' -import { formatDateShort } from '../../lib/timeAgo' - -import type { ProfilePage, WithContext } from 'schema-dts' - -const username = Astro.params.username -if (!username) return Astro.rewrite('/404') - -const user = await Astro.locals.banners.try('user', async () => { - return makeUserWithKarmaUnlocks( - await prisma.user.findUnique({ - where: { name: Astro.params.username }, - select: { - id: true, - name: true, - displayName: true, - link: true, - picture: true, - spammer: true, - verified: true, - admin: true, - verifier: true, - verifiedLink: true, - totalKarma: true, - createdAt: true, - _count: { - select: { - comments: true, - commentVotes: true, - karmaTransactions: true, - }, - }, - karmaTransactions: { - select: { - id: true, - points: true, - action: true, - description: true, - createdAt: true, - comment: { - select: { - id: true, - content: true, - }, - }, - }, - orderBy: { createdAt: 'desc' }, - take: 5, - }, - suggestions: { - select: { - id: true, - type: true, - status: true, - createdAt: true, - service: { - select: { - id: true, - name: true, - slug: true, - }, - }, - }, - where: { service: { serviceVisibility: 'PUBLIC' } }, - orderBy: { createdAt: 'desc' }, - take: 5, - }, - comments: { - select: { - id: true, - content: true, - createdAt: true, - upvotes: true, - status: true, - service: { - select: { - id: true, - name: true, - slug: true, - }, - }, - }, - orderBy: { createdAt: 'desc' }, - take: 5, - }, - commentVotes: { - select: { - id: true, - downvote: true, - createdAt: true, - comment: { - select: { - id: true, - content: true, - service: { - select: { - id: true, - name: true, - slug: true, - }, - }, - }, - }, - }, - orderBy: { createdAt: 'desc' }, - take: 5, - }, - serviceAffiliations: { - select: { - role: true, - service: { - select: { - id: true, - name: true, - slug: true, - imageUrl: true, - verificationStatus: true, - }, - }, - }, - orderBy: [{ role: 'asc' }, { service: { name: 'asc' } }], - }, - }, - }) - ) -}) - -if (!user) return Astro.rewrite('/404') - -const isCurrentUser = !!Astro.locals.user && user.id === Astro.locals.user.id ---- - -, - ]} - breadcrumbs={[ - { - name: 'Users', - url: '/u', - }, - { - name: user.displayName ?? user.name, - url: `/u/${user.name}`, - }, - ]} -> -
-
- { - user.picture ? ( - - ) : ( -
- -
- ) - } -
-

- {user.name} - {isCurrentUser && (You)} -

- {user.displayName &&

{user.displayName}

} -
- { - user.admin && ( - - admin - - ) - } - { - user.verified && ( - - verified - - ) - } - { - user.verifier && ( - - verifier - - ) - } -
-
- -
- -
-
-
-

Profile Information

-
    -
  • - -
    -

    Username

    -

    {user.name}

    -
    -
  • - -
  • - - - -
    -

    Display Name

    - -

    - {user.displayName ?? Not set} -

    -
    -
  • - - { - !!user.link && ( -
  • - - - -
    -

    Website

    - - {user.link} - -
    -
  • - ) - } - -
  • - -
    -

    Karma

    -

    {user.totalKarma.toLocaleString()}

    -
    -
  • -
-
- -
-

Account Status

-
    -
  • - - - -
    -

    Account Type

    -
    - { - user.admin && ( - - Admin - - ) - } - { - user.verified && ( - - Verified User - - ) - } - { - user.verifier && ( - - Verifier - - ) - } - { - !user.admin && !user.verified && !user.verifier && ( - - Standard User - - ) - } -
    -
    -
  • - -
  • - - - -
    -

    Spam Status

    - { - user.spammer ? ( - - Spammer - - ) : ( - - Not Flagged - - ) - } -
    -
  • - -
  • - -
    -

    Joined

    -

    - { - formatDateShort(user.createdAt, { - prefix: false, - hourPrecision: true, - caseType: 'sentence', - }) - } -

    -
    -
  • - - { - user.verifiedLink && ( -
  • - - - -
    -

    Verified as related to

    - - {user.verifiedLink} - -
    -
  • - ) - } -
-
-
-
-
- -
-
-

- - Service Affiliations -

-
- - { - user.serviceAffiliations.length > 0 ? ( - - ) : ( -

No service affiliations yet.

- ) - } -
- -
-
-

- - Karma Unlocks -

- -
-

- Earn karma to unlock features and privileges. Learn about karma -

-
-
- -
-
-

Positive unlocks

- - { - sortBy( - karmaUnlocks.filter((unlock) => unlock.karma >= 0), - 'karma' - ).map((unlock) => ( -
-
- - - -
-

- {unlock.name} -

-

{unlock.karma.toLocaleString()} karma

-
-
-
- {user.karmaUnlocks[unlock.id] ? ( - - Unlocked - - ) : ( - - Locked - - )} -
-
- )) - } -
- -
-

Negative unlocks

- - { - sortBy( - karmaUnlocks.filter((unlock) => unlock.karma < 0), - 'karma' - ) - .reverse() - .map((unlock) => ( -
-
- - - -
-

- {unlock.name} -

-

{unlock.karma.toLocaleString()} karma

-
-
-
- {user.karmaUnlocks[unlock.id] ? ( - - Active - - ) : ( - - Avoided - - )} -
-
- )) - } - -

- - Negative karma leads to restrictions. Keep interactions positive to - avoid penalties. -

-
-
-
- -
-
-
-

- - Recent Comments -

- {user._count.comments.toLocaleString()} comments -
- - { - user.comments.length === 0 ? ( -

No comments yet.

- ) : ( -
- - - - - - - - - - - - {user.comments.map((comment) => ( - - - - - - - - ))} - -
ServiceCommentStatusUpvotesDate
- - {comment.service.name} - - -

{comment.content}

-
- - {comment.status} - - - - {comment.upvotes} - - - -
-
- ) - } -
- - { - user.karmaUnlocks.voteComments || user._count.commentVotes ? ( -
-
-

- - Recent Votes -

- {user._count.commentVotes.toLocaleString()} votes -
- - {user.commentVotes.length === 0 ? ( -

No votes yet.

- ) : ( -
- - - - - - - - - - - {user.commentVotes.map((vote) => ( - - - - - - - ))} - -
ServiceCommentVoteDate
- - {vote.comment.service.name} - - -

{vote.comment.content}

-
- {vote.downvote ? ( - - - - ) : ( - - - - )} - - -
-
- )} -
- ) : ( -
-

- - Recent Votes -

- - - Locked - -
- ) - } - -
-
-

- - Recent Suggestions -

-
- - { - user.suggestions.length === 0 ? ( -

No suggestions yet.

- ) : ( -
- - - - - - - - - - - {user.suggestions.map((suggestion) => { - const typeInfo = getServiceSuggestionTypeInfo(suggestion.type) - const statusInfo = getServiceSuggestionStatusInfo(suggestion.status) - - return ( - - - - - - - ) - })} - -
ServiceTypeStatusDate
- - {suggestion.service.name} - - - - - {typeInfo.label} - - - - - {statusInfo.label} - - - -
-
- ) - } -
- -
-
-

- - Recent Karma Transactions -

- {user.totalKarma.toLocaleString()} karma -
- - { - user.karmaTransactions.length === 0 ? ( -

No karma transactions yet.

- ) : ( -
- - - - - - - - - - - {user.karmaTransactions.map((transaction) => ( - - - - - - - ))} - -
ActionDescriptionPointsDate
{transaction.action}{transaction.description}= 0 ? 'text-green-400' : 'text-red-400' - )} - > - {transaction.points >= 0 && '+'} - {transaction.points} - - -
-
- ) - } -
-
-
diff --git a/web/src/robots.txt.ts b/web/src/robots.txt.ts deleted file mode 100644 index 5a1c5c0..0000000 --- a/web/src/robots.txt.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { APIRoute } from 'astro' - -const getRobotsTxt = (sitemapURL: URL) => ` -User-agent: * -Allow: / -Disallow: /admin/ - -Sitemap: ${sitemapURL.href} -` - -export const GET: APIRoute = ({ site }) => { - const sitemapURL = new URL('sitemap-index.xml', site) - return new Response(getRobotsTxt(sitemapURL)) -} diff --git a/web/src/styles/global.css b/web/src/styles/global.css deleted file mode 100644 index fbaa8d3..0000000 --- a/web/src/styles/global.css +++ /dev/null @@ -1,133 +0,0 @@ -@import 'tailwindcss'; - -@plugin "@tailwindcss/forms"; -@plugin 'tailwind-htmx'; -@plugin '@tailwindcss/typography'; - -@custom-variant theme-midnight { - &:where([data-theme='midnight'] *) { - @slot; - } -} - -@custom-variant js { - &:is(.js *) { - @slot; - } -} - -@custom-variant no-js { - &:not(.js *) { - @slot; - } -} - -@layer base { - button:not(:disabled), - [role='button']:not(:disabled), - label[for] { - cursor: pointer; - } - - input[type='checkbox']:not(.unset) { - @apply border-day-300 checked:ring-day-300 cursor-pointer rounded border-2 text-green-600 shadow-sm ring-0 ring-current ring-offset-0 transition-all duration-100 not-checked:bg-zinc-800 checked:border-current focus-visible:ring-2; - } - - input[type='radio']:not(.unset) { - @apply border-day-300 checked:ring-day-300 cursor-pointer rounded-full border-2 text-green-600 shadow-sm ring-0 ring-current ring-offset-0 transition-all duration-100 not-checked:bg-zinc-800 checked:border-current focus-visible:ring-2; - } - - .prose { - @apply prose-headings:font-title; - } -} - -@theme inline { - --font-title: 'Space Grotesk Variable', var(--font-sans), 'sans-serif'; -} - -@theme { - --text-2xs: 0.65rem; -} - -@theme { - --breakpoint-xs: 475px; - --breakpoint-2xs: 375px; -} - -@theme { - --color-score-1: #e26136; - --color-score-2: #eba370; - --color-score-3: #eddb82; - --color-score-4: #8de2d7; - --color-score-5: #3cdd71; - - --color-score-saturate-1: #ff0000; - --color-score-saturate-2: #ff7300; - --color-score-saturate-3: #ffff00; - --color-score-saturate-4: #00ffff; - --color-score-saturate-5: #7cff00; - - --color-day-50: oklch(97.13% 0.002 165.08); - --color-day-100: oklch(94.73% 0.003 165.07); - --color-day-200: oklch(89.62% 0.006 170.43); - --color-day-300: oklch(83.81% 0.01 171.74); - --color-day-400: oklch(74.95% 0.015 169.09); - --color-day-500: oklch(66.18% 0.02 171.17); - --color-day-600: oklch(56.71% 0.022 172.22); - --color-day-700: oklch(46.55% 0.018 170.35); - --color-day-800: oklch(34.77% 0.012 171.06); - --color-day-900: oklch(30.05% 0.01 179.07); - --color-day-950: oklch(27.08% 0.008 196.85); - - --color-night-50: oklch(90.58% 0.009 171.78); - --color-night-100: oklch(80.76% 0.017 178.15); - --color-night-200: oklch(61.23% 0.035 173.55); - --color-night-300: oklch(36.23% 0.018 174.61); - --color-night-400: oklch(33.22% 0.017 172.96); - --color-night-500: oklch(28.79% 0.013 180.38); - --color-night-600: oklch(25.59% 0.012 178.46); - --color-night-700: oklch(22.09% 0.008 182.2); - --color-night-800: oklch(18.65% 0.007 178.83); - --color-night-900: oklch(13.3% 0.002 196.91); - --color-night-950: oklch(11.97% 0.004 145.32); -} - -@layer utilities { - .text-shadow-glow { - text-shadow: - 0 0 16px color-mix(in oklab, currentColor 30%, transparent), - 0 0 4px color-mix(in oklab, currentColor 60%, transparent); - } - .drop-shadow-glow { - filter: drop-shadow(0 0 16px color-mix(in oklab, currentColor 30%, transparent)) - drop-shadow(0 0 4px color-mix(in oklab, currentColor 60%, transparent)); - } -} - -@theme { - --ease-in-sine: cubic-bezier(0.12, 0, 0.39, 0); - --ease-out-sine: cubic-bezier(0.61, 1, 0.88, 1); - --ease-in-out-sine: cubic-bezier(0.37, 0, 0.63, 1); - --ease-in-quad: cubic-bezier(0.11, 0, 0.5, 0); - --ease-out-quad: cubic-bezier(0.5, 1, 0.89, 1); - --ease-in-out-quad: cubic-bezier(0.45, 0, 0.55, 1); - --ease-in-cubic: cubic-bezier(0.32, 0, 0.67, 0); - --ease-out-cubic: cubic-bezier(0.33, 1, 0.68, 1); - --ease-in-out-cubic: cubic-bezier(0.65, 0, 0.35, 1); - --ease-in-quart: cubic-bezier(0.5, 0, 0.75, 0); - --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1); - --ease-in-out-quart: cubic-bezier(0.76, 0, 0.24, 1); - --ease-in-quint: cubic-bezier(0.64, 0, 0.78, 0); - --ease-out-quint: cubic-bezier(0.22, 1, 0.36, 1); - --ease-in-out-quint: cubic-bezier(0.83, 0, 0.17, 1); - --ease-in-expo: cubic-bezier(0.7, 0, 0.84, 0); - --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1); - --ease-in-out-expo: cubic-bezier(0.87, 0, 0.13, 1); - --ease-in-circ: cubic-bezier(0.55, 0, 1, 0.45); - --ease-out-circ: cubic-bezier(0, 0.55, 0.45, 1); - --ease-in-out-circ: cubic-bezier(0.85, 0, 0.15, 1); - --ease-in-back: cubic-bezier(0.36, 0, 0.66, -0.56); - --ease-out-back: cubic-bezier(0.34, 1.56, 0.64, 1); - --ease-in-out-back: cubic-bezier(0.68, -0.6, 0.32, 1.6); -} diff --git a/web/src/types/eslint-plugin-import.d.ts b/web/src/types/eslint-plugin-import.d.ts deleted file mode 100644 index bdff2c6..0000000 --- a/web/src/types/eslint-plugin-import.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -declare module 'eslint-plugin-import' { - export const configs: { - recommended: any - errors: any - warnings: any - typescript: any - } - export const flatConfigs: { - recommended: any - errors: any - warnings: any - typescript: any - } -} diff --git a/web/tsconfig.json b/web/tsconfig.json deleted file mode 100644 index fcd818f..0000000 --- a/web/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "astro/tsconfigs/strict", - "include": [".astro/types.d.ts", "**/*"], - "exclude": ["dist"], - "compilerOptions": { - "noUncheckedIndexedAccess": true, - "jsx": "react-jsx", - "jsxImportSource": "react" - } -}

I5>2v37+Yzomfeu?KnrL!-^@nvAC~-=SL74d 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