blockmine 1.20.0 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/README.md +469 -0
- package/.claude/agents/auth-route-debugger.md +118 -0
- package/.claude/agents/auth-route-tester.md +93 -0
- package/.claude/agents/auto-error-resolver.md +97 -0
- package/.claude/agents/build-optimizer.md +236 -0
- package/.claude/agents/code-architecture-reviewer.md +83 -0
- package/.claude/agents/code-refactor-master.md +94 -0
- package/.claude/agents/cost-optimizer.md +134 -0
- package/.claude/agents/deployment-orchestrator.md +113 -0
- package/.claude/agents/documentation-architect.md +82 -0
- package/.claude/agents/frontend-error-fixer.md +77 -0
- package/.claude/agents/iac-code-generator.md +71 -0
- package/.claude/agents/incident-responder.md +346 -0
- package/.claude/agents/infrastructure-architect.md +31 -0
- package/.claude/agents/kubernetes-specialist.md +56 -0
- package/.claude/agents/migration-planner.md +181 -0
- package/.claude/agents/network-architect.md +196 -0
- package/.claude/agents/plan-reviewer.md +52 -0
- package/.claude/agents/refactor-planner.md +63 -0
- package/.claude/agents/security-scanner.md +102 -0
- package/.claude/agents/web-research-specialist.md +78 -0
- package/.claude/commands/cost-analysis.md +315 -0
- package/.claude/commands/dev-docs-update.md +55 -0
- package/.claude/commands/dev-docs.md +51 -0
- package/.claude/commands/incident-debug.md +247 -0
- package/.claude/commands/infra-plan.md +81 -0
- package/.claude/commands/migration-plan.md +478 -0
- package/.claude/commands/route-research-for-testing.md +37 -0
- package/.claude/commands/security-review.md +66 -0
- package/.claude/hooks/CONFIG.md +448 -0
- package/.claude/hooks/README.md +163 -0
- package/.claude/hooks/SKILL_ACTIVATION_COMPLETE.md +226 -0
- package/.claude/hooks/WINDOWS_HOOKS_README.md +151 -0
- package/.claude/hooks/add-skill-activation-banners.ts +132 -0
- package/.claude/hooks/comprehensive-skill-test.ts +1315 -0
- package/.claude/hooks/error-handling-reminder.sh +12 -0
- package/.claude/hooks/error-handling-reminder.ts +222 -0
- package/.claude/hooks/k8s-manifest-validator.sh +56 -0
- package/.claude/hooks/package-lock.json +556 -0
- package/.claude/hooks/package.json +16 -0
- package/.claude/hooks/post-tool-use-tracker.ps1 +174 -0
- package/.claude/hooks/post-tool-use-tracker.sh +183 -0
- package/.claude/hooks/security-policy-check.sh +247 -0
- package/.claude/hooks/skill-activation-prompt.ps1 +10 -0
- package/.claude/hooks/skill-activation-prompt.sh +10 -0
- package/.claude/hooks/skill-activation-prompt.ts +141 -0
- package/.claude/hooks/stop-build-check-enhanced.sh +130 -0
- package/.claude/hooks/terraform-validator.sh +53 -0
- package/.claude/hooks/test-input.json +7 -0
- package/.claude/hooks/test-skill-activation.ts +427 -0
- package/.claude/hooks/trigger-build-resolver.sh +79 -0
- package/.claude/hooks/tsc-check.sh +173 -0
- package/.claude/hooks/tsconfig.json +19 -0
- package/.claude/settings.json +55 -0
- package/.claude/settings.local.json +28 -3
- package/.claude/skills/README.md +507 -0
- package/.claude/skills/api-engineering/SKILL.md +63 -0
- package/.claude/skills/api-engineering/resources/api-versioning.md +88 -0
- package/.claude/skills/api-engineering/resources/graphql-patterns.md +106 -0
- package/.claude/skills/api-engineering/resources/rate-limiting.md +118 -0
- package/.claude/skills/api-engineering/resources/rest-api-design.md +105 -0
- package/.claude/skills/backend-dev-guidelines/SKILL.md +306 -0
- package/.claude/skills/backend-dev-guidelines/resources/architecture-overview.md +451 -0
- package/.claude/skills/backend-dev-guidelines/resources/async-and-errors.md +307 -0
- package/.claude/skills/backend-dev-guidelines/resources/complete-examples.md +638 -0
- package/.claude/skills/backend-dev-guidelines/resources/configuration.md +275 -0
- package/.claude/skills/backend-dev-guidelines/resources/database-patterns.md +224 -0
- package/.claude/skills/backend-dev-guidelines/resources/middleware-guide.md +213 -0
- package/.claude/skills/backend-dev-guidelines/resources/routing-and-controllers.md +756 -0
- package/.claude/skills/backend-dev-guidelines/resources/sentry-and-monitoring.md +336 -0
- package/.claude/skills/backend-dev-guidelines/resources/services-and-repositories.md +789 -0
- package/.claude/skills/backend-dev-guidelines/resources/testing-guide.md +235 -0
- package/.claude/skills/backend-dev-guidelines/resources/validation-patterns.md +754 -0
- package/.claude/skills/budget-and-cost-management/SKILL.md +850 -0
- package/.claude/skills/build-engineering/SKILL.md +431 -0
- package/.claude/skills/build-engineering/resources/artifact-repositories.md +72 -0
- package/.claude/skills/build-engineering/resources/build-caching.md +96 -0
- package/.claude/skills/build-engineering/resources/build-pipelines.md +105 -0
- package/.claude/skills/build-engineering/resources/build-security.md +95 -0
- package/.claude/skills/build-engineering/resources/build-systems.md +389 -0
- package/.claude/skills/build-engineering/resources/compilation-optimization.md +201 -0
- package/.claude/skills/build-engineering/resources/dependency-management.md +73 -0
- package/.claude/skills/build-engineering/resources/monorepo-builds.md +110 -0
- package/.claude/skills/build-engineering/resources/performance-optimization.md +113 -0
- package/.claude/skills/build-engineering/resources/reproducible-builds.md +82 -0
- package/.claude/skills/cloud-engineering/SKILL.md +675 -0
- package/.claude/skills/cloud-engineering/resources/aws-patterns.md +742 -0
- package/.claude/skills/cloud-engineering/resources/azure-patterns.md +714 -0
- package/.claude/skills/cloud-engineering/resources/cleared-cloud-environments.md +987 -0
- package/.claude/skills/cloud-engineering/resources/cloud-cost-optimization.md +757 -0
- package/.claude/skills/cloud-engineering/resources/cloud-networking.md +1058 -0
- package/.claude/skills/cloud-engineering/resources/cloud-security-tools.md +1530 -0
- package/.claude/skills/cloud-engineering/resources/cloud-security.md +990 -0
- package/.claude/skills/cloud-engineering/resources/gcp-patterns.md +758 -0
- package/.claude/skills/cloud-engineering/resources/migration-strategies.md +820 -0
- package/.claude/skills/cloud-engineering/resources/multi-cloud-strategies.md +670 -0
- package/.claude/skills/cloud-engineering/resources/oci-patterns.md +1198 -0
- package/.claude/skills/cloud-engineering/resources/serverless-patterns.md +795 -0
- package/.claude/skills/cloud-engineering/resources/well-architected-frameworks.md +966 -0
- package/.claude/skills/cybersecurity/SKILL.md +409 -0
- package/.claude/skills/cybersecurity/resources/security-architecture.md +266 -0
- package/.claude/skills/database-engineering/SKILL.md +61 -0
- package/.claude/skills/database-engineering/resources/backup-and-recovery.md +72 -0
- package/.claude/skills/database-engineering/resources/database-replication.md +63 -0
- package/.claude/skills/database-engineering/resources/postgresql-fundamentals.md +70 -0
- package/.claude/skills/database-engineering/resources/query-optimization.md +68 -0
- package/.claude/skills/devsecops/SKILL.md +374 -0
- package/.claude/skills/devsecops/resources/ci-cd-security.md +204 -0
- package/.claude/skills/devsecops/resources/compliance-automation.md +530 -0
- package/.claude/skills/devsecops/resources/compliance-frameworks.md +2322 -0
- package/.claude/skills/devsecops/resources/container-security.md +915 -0
- package/.claude/skills/devsecops/resources/cspm-integration.md +1440 -0
- package/.claude/skills/devsecops/resources/policy-enforcement.md +619 -0
- package/.claude/skills/devsecops/resources/secrets-management.md +755 -0
- package/.claude/skills/devsecops/resources/security-monitoring.md +146 -0
- package/.claude/skills/devsecops/resources/security-scanning.md +887 -0
- package/.claude/skills/devsecops/resources/security-testing.md +203 -0
- package/.claude/skills/devsecops/resources/supply-chain-security.md +518 -0
- package/.claude/skills/devsecops/resources/vulnerability-management.md +481 -0
- package/.claude/skills/devsecops/resources/zero-trust-architecture.md +177 -0
- package/.claude/skills/documentation-as-code/SKILL.md +323 -0
- package/.claude/skills/documentation-as-code/resources/api-documentation.md +90 -0
- package/.claude/skills/documentation-as-code/resources/changelog-management.md +79 -0
- package/.claude/skills/documentation-as-code/resources/diagram-generation.md +44 -0
- package/.claude/skills/documentation-as-code/resources/docs-as-code-workflow.md +99 -0
- package/.claude/skills/documentation-as-code/resources/documentation-automation.md +68 -0
- package/.claude/skills/documentation-as-code/resources/documentation-sites.md +79 -0
- package/.claude/skills/documentation-as-code/resources/markdown-best-practices.md +162 -0
- package/.claude/skills/documentation-as-code/resources/openapi-specification.md +77 -0
- package/.claude/skills/documentation-as-code/resources/readme-engineering.md +60 -0
- package/.claude/skills/documentation-as-code/resources/technical-writing-guide.md +202 -0
- package/.claude/skills/engineering-management/SKILL.md +356 -0
- package/.claude/skills/engineering-management/resources/career-ladders.md +609 -0
- package/.claude/skills/engineering-management/resources/hiring-and-assessment.md +555 -0
- package/.claude/skills/engineering-management/resources/one-on-one-guides.md +609 -0
- package/.claude/skills/engineering-management/resources/resource-planning.md +557 -0
- package/.claude/skills/engineering-management/resources/team-organization-patterns.md +491 -0
- package/.claude/skills/engineering-management/resources/technical-interviews.md +474 -0
- package/.claude/skills/engineering-operations-management/SKILL.md +817 -0
- package/.claude/skills/error-tracking/SKILL.md +379 -0
- package/.claude/skills/frontend-dev-guidelines/SKILL.md +403 -0
- package/.claude/skills/frontend-dev-guidelines/resources/common-patterns.md +331 -0
- package/.claude/skills/frontend-dev-guidelines/resources/complete-examples.md +872 -0
- package/.claude/skills/frontend-dev-guidelines/resources/component-patterns.md +502 -0
- package/.claude/skills/frontend-dev-guidelines/resources/data-fetching.md +767 -0
- package/.claude/skills/frontend-dev-guidelines/resources/file-organization.md +502 -0
- package/.claude/skills/frontend-dev-guidelines/resources/loading-and-error-states.md +501 -0
- package/.claude/skills/frontend-dev-guidelines/resources/performance.md +406 -0
- package/.claude/skills/frontend-dev-guidelines/resources/routing-guide.md +364 -0
- package/.claude/skills/frontend-dev-guidelines/resources/styling-guide.md +428 -0
- package/.claude/skills/frontend-dev-guidelines/resources/typescript-standards.md +418 -0
- package/.claude/skills/general-it-engineering/SKILL.md +393 -0
- package/.claude/skills/general-it-engineering/resources/asset-management.md +712 -0
- package/.claude/skills/general-it-engineering/resources/automation-orchestration.md +817 -0
- package/.claude/skills/general-it-engineering/resources/business-continuity.md +786 -0
- package/.claude/skills/general-it-engineering/resources/change-management.md +715 -0
- package/.claude/skills/general-it-engineering/resources/enterprise-monitoring.md +729 -0
- package/.claude/skills/general-it-engineering/resources/help-desk-operations.md +738 -0
- package/.claude/skills/general-it-engineering/resources/incident-service-management.md +834 -0
- package/.claude/skills/general-it-engineering/resources/it-governance.md +753 -0
- package/.claude/skills/general-it-engineering/resources/itil-framework.md +503 -0
- package/.claude/skills/general-it-engineering/resources/service-management.md +669 -0
- package/.claude/skills/infrastructure-architecture/SKILL.md +328 -0
- package/.claude/skills/infrastructure-architecture/resources/architecture-decision-records.md +505 -0
- package/.claude/skills/infrastructure-architecture/resources/architecture-patterns.md +528 -0
- package/.claude/skills/infrastructure-architecture/resources/capacity-planning.md +453 -0
- package/.claude/skills/infrastructure-architecture/resources/cleared-environment-architecture.md +773 -0
- package/.claude/skills/infrastructure-architecture/resources/cost-architecture.md +499 -0
- package/.claude/skills/infrastructure-architecture/resources/data-architecture.md +501 -0
- package/.claude/skills/infrastructure-architecture/resources/disaster-recovery.md +535 -0
- package/.claude/skills/infrastructure-architecture/resources/migration-architecture.md +512 -0
- package/.claude/skills/infrastructure-architecture/resources/multi-region-design.md +608 -0
- package/.claude/skills/infrastructure-architecture/resources/reference-architectures.md +562 -0
- package/.claude/skills/infrastructure-architecture/resources/security-architecture.md +538 -0
- package/.claude/skills/infrastructure-architecture/resources/system-design-principles.md +489 -0
- package/.claude/skills/infrastructure-architecture/resources/workload-classification.md +1000 -0
- package/.claude/skills/infrastructure-strategy/SKILL.md +924 -0
- package/.claude/skills/network-engineering/SKILL.md +385 -0
- package/.claude/skills/network-engineering/resources/dns-management.md +738 -0
- package/.claude/skills/network-engineering/resources/load-balancing.md +820 -0
- package/.claude/skills/network-engineering/resources/network-architecture.md +546 -0
- package/.claude/skills/network-engineering/resources/network-security.md +921 -0
- package/.claude/skills/network-engineering/resources/network-troubleshooting.md +749 -0
- package/.claude/skills/network-engineering/resources/routing-switching.md +373 -0
- package/.claude/skills/network-engineering/resources/sdn-networking.md +695 -0
- package/.claude/skills/network-engineering/resources/service-mesh-networking.md +777 -0
- package/.claude/skills/network-engineering/resources/tcp-ip-protocols.md +444 -0
- package/.claude/skills/network-engineering/resources/vpn-connectivity.md +672 -0
- package/.claude/skills/observability-engineering/SKILL.md +101 -0
- package/.claude/skills/observability-engineering/resources/apm-tools.md +97 -0
- package/.claude/skills/observability-engineering/resources/correlation-strategies.md +87 -0
- package/.claude/skills/observability-engineering/resources/distributed-tracing.md +98 -0
- package/.claude/skills/observability-engineering/resources/logs-aggregation.md +118 -0
- package/.claude/skills/observability-engineering/resources/observability-cost-optimization.md +141 -0
- package/.claude/skills/observability-engineering/resources/opentelemetry.md +110 -0
- package/.claude/skills/platform-engineering/SKILL.md +555 -0
- package/.claude/skills/platform-engineering/resources/architecture-overview.md +600 -0
- package/.claude/skills/platform-engineering/resources/container-orchestration.md +916 -0
- package/.claude/skills/platform-engineering/resources/cost-optimization.md +634 -0
- package/.claude/skills/platform-engineering/resources/developer-platforms.md +670 -0
- package/.claude/skills/platform-engineering/resources/gitops-automation.md +650 -0
- package/.claude/skills/platform-engineering/resources/infrastructure-as-code.md +778 -0
- package/.claude/skills/platform-engineering/resources/infrastructure-standards.md +708 -0
- package/.claude/skills/platform-engineering/resources/multi-tenancy.md +602 -0
- package/.claude/skills/platform-engineering/resources/platform-security.md +711 -0
- package/.claude/skills/platform-engineering/resources/resource-management.md +592 -0
- package/.claude/skills/platform-engineering/resources/service-mesh.md +628 -0
- package/.claude/skills/release-engineering/SKILL.md +393 -0
- package/.claude/skills/release-engineering/resources/artifact-management.md +108 -0
- package/.claude/skills/release-engineering/resources/build-optimization.md +84 -0
- package/.claude/skills/release-engineering/resources/ci-cd-pipelines.md +411 -0
- package/.claude/skills/release-engineering/resources/deployment-strategies.md +197 -0
- package/.claude/skills/release-engineering/resources/pipeline-security.md +62 -0
- package/.claude/skills/release-engineering/resources/progressive-delivery.md +83 -0
- package/.claude/skills/release-engineering/resources/release-automation.md +68 -0
- package/.claude/skills/release-engineering/resources/release-orchestration.md +77 -0
- package/.claude/skills/release-engineering/resources/rollback-strategies.md +66 -0
- package/.claude/skills/release-engineering/resources/versioning-strategies.md +59 -0
- package/.claude/skills/route-tester/SKILL.md +392 -0
- package/.claude/skills/skill-developer/ADVANCED.md +197 -0
- package/.claude/skills/skill-developer/HOOK_MECHANISMS.md +306 -0
- package/.claude/skills/skill-developer/PATTERNS_LIBRARY.md +152 -0
- package/.claude/skills/skill-developer/SKILL.md +430 -0
- package/.claude/skills/skill-developer/SKILL_RULES_REFERENCE.md +315 -0
- package/.claude/skills/skill-developer/TRIGGER_TYPES.md +305 -0
- package/.claude/skills/skill-developer/TROUBLESHOOTING.md +514 -0
- package/.claude/skills/skill-rules.json +2940 -0
- package/.claude/skills/sre/SKILL.md +464 -0
- package/.claude/skills/sre/resources/alerting-best-practices.md +282 -0
- package/.claude/skills/sre/resources/capacity-planning.md +226 -0
- package/.claude/skills/sre/resources/chaos-engineering.md +193 -0
- package/.claude/skills/sre/resources/disaster-recovery.md +232 -0
- package/.claude/skills/sre/resources/incident-management.md +436 -0
- package/.claude/skills/sre/resources/observability-stack.md +240 -0
- package/.claude/skills/sre/resources/on-call-runbooks.md +167 -0
- package/.claude/skills/sre/resources/performance-optimization.md +108 -0
- package/.claude/skills/sre/resources/reliability-patterns.md +183 -0
- package/.claude/skills/sre/resources/slo-sli-sla.md +464 -0
- package/.claude/skills/sre/resources/toil-reduction.md +145 -0
- package/.claude/skills/systems-engineering/SKILL.md +648 -0
- package/.claude/skills/systems-engineering/resources/automation-patterns.md +771 -0
- package/.claude/skills/systems-engineering/resources/configuration-management.md +998 -0
- package/.claude/skills/systems-engineering/resources/linux-administration.md +672 -0
- package/.claude/skills/systems-engineering/resources/networking-fundamentals.md +982 -0
- package/.claude/skills/systems-engineering/resources/performance-tuning.md +871 -0
- package/.claude/skills/systems-engineering/resources/powershell-scripting.md +482 -0
- package/.claude/skills/systems-engineering/resources/security-hardening.md +739 -0
- package/.claude/skills/systems-engineering/resources/shell-scripting.md +915 -0
- package/.claude/skills/systems-engineering/resources/storage-management.md +628 -0
- package/.claude/skills/systems-engineering/resources/system-monitoring.md +787 -0
- package/.claude/skills/systems-engineering/resources/troubleshooting-guide.md +753 -0
- package/.claude/skills/systems-engineering/resources/windows-administration.md +738 -0
- package/.claude/skills/technical-leadership/SKILL.md +728 -0
- package/CHANGELOG.md +90 -39
- package/README.md +94 -0
- package/backend/docs/SECRETS_DOCUMENTATION.md +327 -0
- package/backend/jest.config.js +59 -0
- package/backend/package-lock.json +6129 -0
- package/backend/package.json +16 -4
- package/backend/prisma/migrations/20251026104609_add_websocket_api/migration.sql +33 -0
- package/backend/prisma/schema.prisma +33 -0
- package/backend/src/__tests__/core/DependencyService.test.js +336 -0
- package/backend/src/__tests__/core/UserService.test.js +875 -0
- package/backend/src/__tests__/repositories/BaseRepository.test.js +146 -0
- package/backend/src/__tests__/repositories/BotRepository.test.js +118 -0
- package/backend/src/__tests__/repositories/CommandRepository.test.js +132 -0
- package/backend/src/__tests__/repositories/EventGraphRepository.test.js +93 -0
- package/backend/src/__tests__/repositories/GroupRepository.test.js +155 -0
- package/backend/src/__tests__/repositories/PermissionRepository.test.js +130 -0
- package/backend/src/__tests__/repositories/PluginRepository.test.js +107 -0
- package/backend/src/__tests__/repositories/ServerRepository.test.js +80 -0
- package/backend/src/__tests__/repositories/UserRepository.test.js +128 -0
- package/backend/src/__tests__/secretsFilter.test.js +425 -0
- package/backend/src/__tests__/services/BotLifecycleService.test.js +411 -0
- package/backend/src/__tests__/services/BotProcessManager.test.js +285 -0
- package/backend/src/__tests__/services/CacheManager.test.js +125 -0
- package/backend/src/__tests__/services/CommandExecutionService.test.js +460 -0
- package/backend/src/__tests__/services/ResourceMonitorService.test.js +207 -0
- package/backend/src/__tests__/services/TelemetryService.test.js +291 -0
- package/backend/src/__tests__/setup.js +25 -0
- package/backend/src/api/routes/apiKeys.js +181 -0
- package/backend/src/api/routes/bots.js +49 -7
- package/backend/src/api/routes/plugins.js +2 -1
- package/backend/src/api/routes/system.js +174 -0
- package/backend/src/container.js +82 -0
- package/backend/src/core/BotManager.js +142 -871
- package/backend/src/core/BotManager.old.js +1093 -0
- package/backend/src/core/BotProcess.js +1092 -850
- package/backend/src/core/BreakLoopSignal.js +8 -0
- package/backend/src/core/EventGraphManager.js +280 -193
- package/backend/src/core/GraphExecutionEngine.js +321 -928
- package/backend/src/core/MessageQueue.js +27 -6
- package/backend/src/core/NodeRegistry.js +37 -991
- package/backend/src/core/PluginManager.js +62 -12
- package/backend/src/core/PrismaService.js +32 -0
- package/backend/src/core/UserService.js +3 -3
- package/backend/src/core/__tests__/PrismaService.test.js +24 -0
- package/backend/src/core/commands/README.md +305 -0
- package/backend/src/core/commands/dev.js +13 -7
- package/backend/src/core/commands/ping.js +10 -4
- package/backend/src/core/commands/whois.js +63 -0
- package/backend/src/core/config/validation.js +27 -0
- package/backend/src/core/constants/graphTypes.js +21 -0
- package/backend/src/core/node-registries/actions.js +132 -0
- package/backend/src/core/node-registries/arrays.js +137 -0
- package/backend/src/core/node-registries/bot.js +23 -0
- package/backend/src/core/node-registries/data.js +290 -0
- package/backend/src/core/node-registries/debug.js +26 -0
- package/backend/src/core/node-registries/events.js +187 -0
- package/backend/src/core/node-registries/flow.js +139 -0
- package/backend/src/core/node-registries/logic.js +45 -0
- package/backend/src/core/node-registries/math.js +42 -0
- package/backend/src/core/node-registries/objects.js +98 -0
- package/backend/src/core/node-registries/strings.js +153 -0
- package/backend/src/core/node-registries/time.js +113 -0
- package/backend/src/core/node-registries/users.js +79 -0
- package/backend/src/core/nodes/actions/bot_look_at.js +36 -0
- package/backend/src/core/nodes/actions/bot_set_variable.js +32 -0
- package/backend/src/core/nodes/actions/http_request.js +98 -0
- package/backend/src/core/nodes/actions/send_log.js +28 -0
- package/backend/src/core/nodes/actions/send_message.js +32 -0
- package/backend/src/core/nodes/actions/send_websocket_response.js +33 -0
- package/backend/src/core/nodes/arrays/add_element.js +23 -0
- package/backend/src/core/nodes/arrays/contains.js +40 -0
- package/backend/src/core/nodes/arrays/find_index.js +23 -0
- package/backend/src/core/nodes/arrays/get_by_index.js +23 -0
- package/backend/src/core/nodes/arrays/get_next.js +35 -0
- package/backend/src/core/nodes/arrays/get_random_element.js +32 -0
- package/backend/src/core/nodes/arrays/remove_by_index.js +30 -0
- package/backend/src/core/nodes/bot/get_position.js +20 -0
- package/backend/src/core/nodes/data/array_literal.js +31 -0
- package/backend/src/core/nodes/data/boolean_literal.js +21 -0
- package/backend/src/core/nodes/data/cast.js +42 -0
- package/backend/src/core/nodes/data/datetime_literal.js +27 -0
- package/backend/src/core/nodes/data/entity_info.js +69 -0
- package/backend/src/core/nodes/data/get_argument.js +23 -0
- package/backend/src/core/nodes/data/get_bot_look.js +14 -0
- package/backend/src/core/nodes/data/get_entity_field.js +18 -0
- package/backend/src/core/nodes/data/get_nearby_entities.js +32 -0
- package/backend/src/core/nodes/data/get_nearby_players.js +64 -0
- package/backend/src/core/nodes/data/get_server_players.js +18 -0
- package/backend/src/core/nodes/data/get_user_field.js +40 -0
- package/backend/src/core/nodes/data/get_variable.js +23 -0
- package/backend/src/core/nodes/data/length.js +25 -0
- package/backend/src/core/nodes/data/make_object.js +31 -0
- package/backend/src/core/nodes/data/number_literal.js +21 -0
- package/backend/src/core/nodes/data/string_literal.js +34 -0
- package/backend/src/core/nodes/data/type_check.js +53 -0
- package/backend/src/core/nodes/debug/log.js +16 -0
- package/backend/src/core/nodes/flow/branch.js +15 -0
- package/backend/src/core/nodes/flow/break.js +14 -0
- package/backend/src/core/nodes/flow/delay.js +43 -0
- package/backend/src/core/nodes/flow/for_each.js +39 -0
- package/backend/src/core/nodes/flow/sequence.js +16 -0
- package/backend/src/core/nodes/flow/switch.js +47 -0
- package/backend/src/core/nodes/flow/while.js +64 -0
- package/backend/src/core/nodes/logic/__tests__/compare.test.js +83 -0
- package/backend/src/core/nodes/logic/compare.js +33 -0
- package/backend/src/core/nodes/logic/operation.js +35 -0
- package/backend/src/core/nodes/math/__tests__/operation.test.js +65 -0
- package/backend/src/core/nodes/math/operation.js +31 -0
- package/backend/src/core/nodes/math/random_number.js +43 -0
- package/backend/src/core/nodes/objects/create.js +40 -0
- package/backend/src/core/nodes/objects/delete.js +26 -0
- package/backend/src/core/nodes/objects/get.js +23 -0
- package/backend/src/core/nodes/objects/has_key.js +30 -0
- package/backend/src/core/nodes/objects/set.js +27 -0
- package/backend/src/core/nodes/strings/__tests__/concat.test.js +89 -0
- package/backend/src/core/nodes/strings/concat.js +27 -0
- package/backend/src/core/nodes/strings/contains.js +41 -0
- package/backend/src/core/nodes/strings/ends_with.js +43 -0
- package/backend/src/core/nodes/strings/equals.js +36 -0
- package/backend/src/core/nodes/strings/length.js +36 -0
- package/backend/src/core/nodes/strings/matches.js +39 -0
- package/backend/src/core/nodes/strings/split.js +37 -0
- package/backend/src/core/nodes/strings/starts_with.js +43 -0
- package/backend/src/core/nodes/time/__tests__/now.test.js +24 -0
- package/backend/src/core/nodes/time/add.js +33 -0
- package/backend/src/core/nodes/time/compare.js +35 -0
- package/backend/src/core/nodes/time/diff.js +29 -0
- package/backend/src/core/nodes/time/format.js +32 -0
- package/backend/src/core/nodes/time/now.js +18 -0
- package/backend/src/core/nodes/users/check_blacklist.js +37 -0
- package/backend/src/core/nodes/users/get_groups.js +36 -0
- package/backend/src/core/nodes/users/get_permissions.js +36 -0
- package/backend/src/core/nodes/users/set_blacklist.js +37 -0
- package/backend/src/core/services/BotLifecycleService.js +596 -0
- package/backend/src/core/services/BotProcessManager.js +163 -0
- package/backend/src/core/services/CacheManager.js +111 -0
- package/backend/src/core/services/CommandExecutionService.js +351 -0
- package/backend/src/core/services/ResourceMonitorService.js +90 -0
- package/backend/src/core/services/TelemetryService.js +124 -0
- package/backend/src/core/services/ValidationService.js +132 -0
- package/backend/src/core/services/__tests__/ValidationService.test.js +148 -0
- package/backend/src/core/services.js +20 -5
- package/backend/src/core/system/CommandContext.js +84 -0
- package/backend/src/core/system/Transport.js +78 -0
- package/backend/src/core/utils/__tests__/jsonParser.test.js +44 -0
- package/backend/src/core/utils/jsonParser.js +18 -0
- package/backend/src/core/utils/secretsFilter.js +262 -0
- package/backend/src/core/utils/variableParser.js +89 -0
- package/backend/src/core/validation/__tests__/nodeSchemas.test.js +175 -0
- package/backend/src/core/validation/nodeSchemas.js +112 -0
- package/backend/src/lib/prisma.js +2 -4
- package/backend/src/real-time/botApi/handlers/commandHandlers.js +28 -0
- package/backend/src/real-time/botApi/handlers/graphHandlers.js +99 -0
- package/backend/src/real-time/botApi/handlers/graphWebSocketHandlers.js +147 -0
- package/backend/src/real-time/botApi/handlers/index.js +43 -0
- package/backend/src/real-time/botApi/handlers/messageHandlers.js +66 -0
- package/backend/src/real-time/botApi/handlers/statusHandlers.js +17 -0
- package/backend/src/real-time/botApi/handlers/userHandlers.js +141 -0
- package/backend/src/real-time/botApi/index.js +40 -0
- package/backend/src/real-time/botApi/middleware.js +79 -0
- package/backend/src/real-time/botApi/utils.js +54 -0
- package/backend/src/real-time/socketHandler.js +6 -2
- package/backend/src/repositories/BaseRepository.js +43 -0
- package/backend/src/repositories/BotRepository.js +42 -0
- package/backend/src/repositories/CommandRepository.js +53 -0
- package/backend/src/repositories/EventGraphRepository.js +40 -0
- package/backend/src/repositories/GroupRepository.js +69 -0
- package/backend/src/repositories/PermissionRepository.js +48 -0
- package/backend/src/repositories/PluginRepository.js +42 -0
- package/backend/src/repositories/ServerRepository.js +27 -0
- package/backend/src/repositories/UserRepository.js +48 -0
- package/backend/src/server.js +3 -0
- package/backend/src/test-refactor.js +85 -0
- package/frontend/dist/assets/index-CfTo92bP.css +1 -0
- package/frontend/dist/assets/index-CiFD5X9Z.js +8344 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/package.json +1 -5
- package/package.json +2 -1
- package/frontend/dist/assets/index-BFd7YoAj.css +0 -1
- package/frontend/dist/assets/index-CMMutadc.js +0 -8352
- package/nul +0 -0
|
@@ -0,0 +1,789 @@
|
|
|
1
|
+
# Services and Repositories - Business Logic Layer
|
|
2
|
+
|
|
3
|
+
Complete guide to organizing business logic with services and data access with repositories.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Service Layer Overview](#service-layer-overview)
|
|
8
|
+
- [Dependency Injection Pattern](#dependency-injection-pattern)
|
|
9
|
+
- [Singleton Pattern](#singleton-pattern)
|
|
10
|
+
- [Repository Pattern](#repository-pattern)
|
|
11
|
+
- [Service Design Principles](#service-design-principles)
|
|
12
|
+
- [Caching Strategies](#caching-strategies)
|
|
13
|
+
- [Testing Services](#testing-services)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Service Layer Overview
|
|
18
|
+
|
|
19
|
+
### Purpose of Services
|
|
20
|
+
|
|
21
|
+
**Services contain business logic** - the 'what' and 'why' of your application:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
Controller asks: "Should I do this?"
|
|
25
|
+
Service answers: "Yes/No, here's why, and here's what happens"
|
|
26
|
+
Repository executes: "Here's the data you requested"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Services are responsible for:**
|
|
30
|
+
- ✅ Business rules enforcement
|
|
31
|
+
- ✅ Orchestrating multiple repositories
|
|
32
|
+
- ✅ Transaction management
|
|
33
|
+
- ✅ Complex calculations
|
|
34
|
+
- ✅ External service integration
|
|
35
|
+
- ✅ Business validations
|
|
36
|
+
|
|
37
|
+
**Services should NOT:**
|
|
38
|
+
- ❌ Know about HTTP (Request/Response)
|
|
39
|
+
- ❌ Direct Prisma access (use repositories)
|
|
40
|
+
- ❌ Handle route-specific logic
|
|
41
|
+
- ❌ Format HTTP responses
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Dependency Injection Pattern
|
|
46
|
+
|
|
47
|
+
### Why Dependency Injection?
|
|
48
|
+
|
|
49
|
+
**Benefits:**
|
|
50
|
+
- Easy to test (inject mocks)
|
|
51
|
+
- Clear dependencies
|
|
52
|
+
- Flexible configuration
|
|
53
|
+
- Promotes loose coupling
|
|
54
|
+
|
|
55
|
+
### Excellent Example: NotificationService
|
|
56
|
+
|
|
57
|
+
**File:** `/blog-api/src/services/NotificationService.ts`
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// Define dependencies interface for clarity
|
|
61
|
+
export interface NotificationServiceDependencies {
|
|
62
|
+
prisma: PrismaClient;
|
|
63
|
+
batchingService: BatchingService;
|
|
64
|
+
emailComposer: EmailComposer;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Service with dependency injection
|
|
68
|
+
export class NotificationService {
|
|
69
|
+
private prisma: PrismaClient;
|
|
70
|
+
private batchingService: BatchingService;
|
|
71
|
+
private emailComposer: EmailComposer;
|
|
72
|
+
private preferencesCache: Map<string, { preferences: UserPreference; timestamp: number }> = new Map();
|
|
73
|
+
private CACHE_TTL = (notificationConfig.preferenceCacheTTLMinutes || 5) * 60 * 1000;
|
|
74
|
+
|
|
75
|
+
// Dependencies injected via constructor
|
|
76
|
+
constructor(dependencies: NotificationServiceDependencies) {
|
|
77
|
+
this.prisma = dependencies.prisma;
|
|
78
|
+
this.batchingService = dependencies.batchingService;
|
|
79
|
+
this.emailComposer = dependencies.emailComposer;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Create a notification and route it appropriately
|
|
84
|
+
*/
|
|
85
|
+
async createNotification(params: CreateNotificationParams) {
|
|
86
|
+
const { recipientID, type, title, message, link, context = {}, channel = 'both', priority = NotificationPriority.NORMAL } = params;
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
// Get template and render content
|
|
90
|
+
const template = getNotificationTemplate(type);
|
|
91
|
+
const rendered = renderNotificationContent(template, context);
|
|
92
|
+
|
|
93
|
+
// Create in-app notification record
|
|
94
|
+
const notificationId = await createNotificationRecord({
|
|
95
|
+
instanceId: parseInt(context.instanceId || '0', 10),
|
|
96
|
+
template: type,
|
|
97
|
+
recipientUserId: recipientID,
|
|
98
|
+
channel: channel === 'email' ? 'email' : 'inApp',
|
|
99
|
+
contextData: context,
|
|
100
|
+
title: finalTitle,
|
|
101
|
+
message: finalMessage,
|
|
102
|
+
link: finalLink,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Route notification based on channel
|
|
106
|
+
if (channel === 'email' || channel === 'both') {
|
|
107
|
+
await this.routeNotification({
|
|
108
|
+
notificationId,
|
|
109
|
+
userId: recipientID,
|
|
110
|
+
type,
|
|
111
|
+
priority,
|
|
112
|
+
title: finalTitle,
|
|
113
|
+
message: finalMessage,
|
|
114
|
+
link: finalLink,
|
|
115
|
+
context,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return notification;
|
|
120
|
+
} catch (error) {
|
|
121
|
+
ErrorLogger.log(error, {
|
|
122
|
+
context: {
|
|
123
|
+
'[NotificationService] createNotification': {
|
|
124
|
+
type: params.type,
|
|
125
|
+
recipientID: params.recipientID,
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Route notification based on user preferences
|
|
135
|
+
*/
|
|
136
|
+
private async routeNotification(params: { notificationId: number; userId: string; type: string; priority: NotificationPriority; title: string; message: string; link?: string; context?: Record<string, any> }) {
|
|
137
|
+
// Get user preferences with caching
|
|
138
|
+
const preferences = await this.getUserPreferences(params.userId);
|
|
139
|
+
|
|
140
|
+
// Check if we should batch or send immediately
|
|
141
|
+
if (this.shouldBatchEmail(preferences, params.type, params.priority)) {
|
|
142
|
+
await this.batchingService.queueNotificationForBatch({
|
|
143
|
+
notificationId: params.notificationId,
|
|
144
|
+
userId: params.userId,
|
|
145
|
+
userPreference: preferences,
|
|
146
|
+
priority: params.priority,
|
|
147
|
+
});
|
|
148
|
+
} else {
|
|
149
|
+
// Send immediately via EmailComposer
|
|
150
|
+
await this.sendImmediateEmail({
|
|
151
|
+
userId: params.userId,
|
|
152
|
+
title: params.title,
|
|
153
|
+
message: params.message,
|
|
154
|
+
link: params.link,
|
|
155
|
+
context: params.context,
|
|
156
|
+
type: params.type,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Determine if email should be batched
|
|
163
|
+
*/
|
|
164
|
+
shouldBatchEmail(preferences: UserPreference, notificationType: string, priority: NotificationPriority): boolean {
|
|
165
|
+
// HIGH priority always immediate
|
|
166
|
+
if (priority === NotificationPriority.HIGH) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Check batch mode
|
|
171
|
+
const batchMode = preferences.emailBatchMode || BatchMode.IMMEDIATE;
|
|
172
|
+
return batchMode !== BatchMode.IMMEDIATE;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get user preferences with caching
|
|
177
|
+
*/
|
|
178
|
+
async getUserPreferences(userId: string): Promise<UserPreference> {
|
|
179
|
+
// Check cache first
|
|
180
|
+
const cached = this.preferencesCache.get(userId);
|
|
181
|
+
if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) {
|
|
182
|
+
return cached.preferences;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const preference = await this.prisma.userPreference.findUnique({
|
|
186
|
+
where: { userID: userId },
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const finalPreferences = preference || DEFAULT_PREFERENCES;
|
|
190
|
+
|
|
191
|
+
// Update cache
|
|
192
|
+
this.preferencesCache.set(userId, {
|
|
193
|
+
preferences: finalPreferences,
|
|
194
|
+
timestamp: Date.now(),
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return finalPreferences;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Usage in Controller:**
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
// Instantiate with dependencies
|
|
206
|
+
const notificationService = new NotificationService({
|
|
207
|
+
prisma: PrismaService.main,
|
|
208
|
+
batchingService: new BatchingService(PrismaService.main),
|
|
209
|
+
emailComposer: new EmailComposer(),
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Use in controller
|
|
213
|
+
const notification = await notificationService.createNotification({
|
|
214
|
+
recipientID: 'user-123',
|
|
215
|
+
type: 'AFRLWorkflowNotification',
|
|
216
|
+
context: { workflowName: 'AFRL Monthly Report' },
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Key Takeaways:**
|
|
221
|
+
- Dependencies passed via constructor
|
|
222
|
+
- Clear interface defines required dependencies
|
|
223
|
+
- Easy to test (inject mocks)
|
|
224
|
+
- Encapsulated caching logic
|
|
225
|
+
- Business rules isolated from HTTP
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Singleton Pattern
|
|
230
|
+
|
|
231
|
+
### When to Use Singletons
|
|
232
|
+
|
|
233
|
+
**Use for:**
|
|
234
|
+
- Services with expensive initialization
|
|
235
|
+
- Services with shared state (caching)
|
|
236
|
+
- Services accessed from many places
|
|
237
|
+
- Permission services
|
|
238
|
+
- Configuration services
|
|
239
|
+
|
|
240
|
+
### Example: PermissionService (Singleton)
|
|
241
|
+
|
|
242
|
+
**File:** `/blog-api/src/services/permissionService.ts`
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
import { PrismaClient } from '@prisma/client';
|
|
246
|
+
|
|
247
|
+
class PermissionService {
|
|
248
|
+
private static instance: PermissionService;
|
|
249
|
+
private prisma: PrismaClient;
|
|
250
|
+
private permissionCache: Map<string, { canAccess: boolean; timestamp: number }> = new Map();
|
|
251
|
+
private CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
252
|
+
|
|
253
|
+
// Private constructor prevents direct instantiation
|
|
254
|
+
private constructor() {
|
|
255
|
+
this.prisma = PrismaService.main;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Get singleton instance
|
|
259
|
+
public static getInstance(): PermissionService {
|
|
260
|
+
if (!PermissionService.instance) {
|
|
261
|
+
PermissionService.instance = new PermissionService();
|
|
262
|
+
}
|
|
263
|
+
return PermissionService.instance;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Check if user can complete a workflow step
|
|
268
|
+
*/
|
|
269
|
+
async canCompleteStep(userId: string, stepInstanceId: number): Promise<boolean> {
|
|
270
|
+
const cacheKey = `${userId}:${stepInstanceId}`;
|
|
271
|
+
|
|
272
|
+
// Check cache
|
|
273
|
+
const cached = this.permissionCache.get(cacheKey);
|
|
274
|
+
if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) {
|
|
275
|
+
return cached.canAccess;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
const post = await this.prisma.post.findUnique({
|
|
280
|
+
where: { id: postId },
|
|
281
|
+
include: {
|
|
282
|
+
author: true,
|
|
283
|
+
comments: {
|
|
284
|
+
include: {
|
|
285
|
+
user: true,
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
if (!post) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Check if user has permission
|
|
296
|
+
const canEdit = post.authorId === userId ||
|
|
297
|
+
await this.isUserAdmin(userId);
|
|
298
|
+
|
|
299
|
+
// Cache result
|
|
300
|
+
this.permissionCache.set(cacheKey, {
|
|
301
|
+
canAccess: isAssigned,
|
|
302
|
+
timestamp: Date.now(),
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
return isAssigned;
|
|
306
|
+
} catch (error) {
|
|
307
|
+
console.error('[PermissionService] Error checking step permission:', error);
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Clear cache for user
|
|
314
|
+
*/
|
|
315
|
+
clearUserCache(userId: string): void {
|
|
316
|
+
for (const [key] of this.permissionCache) {
|
|
317
|
+
if (key.startsWith(`${userId}:`)) {
|
|
318
|
+
this.permissionCache.delete(key);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Clear all cache
|
|
325
|
+
*/
|
|
326
|
+
clearCache(): void {
|
|
327
|
+
this.permissionCache.clear();
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Export singleton instance
|
|
332
|
+
export const permissionService = PermissionService.getInstance();
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
**Usage:**
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
import { permissionService } from '../services/permissionService';
|
|
339
|
+
|
|
340
|
+
// Use anywhere in the codebase
|
|
341
|
+
const canComplete = await permissionService.canCompleteStep(userId, stepId);
|
|
342
|
+
|
|
343
|
+
if (!canComplete) {
|
|
344
|
+
throw new ForbiddenError('You do not have permission to complete this step');
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## Repository Pattern
|
|
351
|
+
|
|
352
|
+
### Purpose of Repositories
|
|
353
|
+
|
|
354
|
+
**Repositories abstract data access** - the 'how' of data operations:
|
|
355
|
+
|
|
356
|
+
```
|
|
357
|
+
Service: "Get me all active users sorted by name"
|
|
358
|
+
Repository: "Here's the Prisma query that does that"
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**Repositories are responsible for:**
|
|
362
|
+
- ✅ All Prisma operations
|
|
363
|
+
- ✅ Query construction
|
|
364
|
+
- ✅ Query optimization (select, include)
|
|
365
|
+
- ✅ Database error handling
|
|
366
|
+
- ✅ Caching database results
|
|
367
|
+
|
|
368
|
+
**Repositories should NOT:**
|
|
369
|
+
- ❌ Contain business logic
|
|
370
|
+
- ❌ Know about HTTP
|
|
371
|
+
- ❌ Make decisions (that's service layer)
|
|
372
|
+
|
|
373
|
+
### Repository Template
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
// repositories/UserRepository.ts
|
|
377
|
+
import { PrismaService } from '@project-lifecycle-portal/database';
|
|
378
|
+
import type { User, Prisma } from '@project-lifecycle-portal/database';
|
|
379
|
+
|
|
380
|
+
export class UserRepository {
|
|
381
|
+
/**
|
|
382
|
+
* Find user by ID with optimized query
|
|
383
|
+
*/
|
|
384
|
+
async findById(userId: string): Promise<User | null> {
|
|
385
|
+
try {
|
|
386
|
+
return await PrismaService.main.user.findUnique({
|
|
387
|
+
where: { userID: userId },
|
|
388
|
+
select: {
|
|
389
|
+
userID: true,
|
|
390
|
+
email: true,
|
|
391
|
+
name: true,
|
|
392
|
+
isActive: true,
|
|
393
|
+
roles: true,
|
|
394
|
+
createdAt: true,
|
|
395
|
+
updatedAt: true,
|
|
396
|
+
},
|
|
397
|
+
});
|
|
398
|
+
} catch (error) {
|
|
399
|
+
console.error('[UserRepository] Error finding user by ID:', error);
|
|
400
|
+
throw new Error(`Failed to find user: ${userId}`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Find all active users
|
|
406
|
+
*/
|
|
407
|
+
async findActive(options?: { orderBy?: Prisma.UserOrderByWithRelationInput }): Promise<User[]> {
|
|
408
|
+
try {
|
|
409
|
+
return await PrismaService.main.user.findMany({
|
|
410
|
+
where: { isActive: true },
|
|
411
|
+
orderBy: options?.orderBy || { name: 'asc' },
|
|
412
|
+
select: {
|
|
413
|
+
userID: true,
|
|
414
|
+
email: true,
|
|
415
|
+
name: true,
|
|
416
|
+
roles: true,
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
} catch (error) {
|
|
420
|
+
console.error('[UserRepository] Error finding active users:', error);
|
|
421
|
+
throw new Error('Failed to find active users');
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Find user by email
|
|
427
|
+
*/
|
|
428
|
+
async findByEmail(email: string): Promise<User | null> {
|
|
429
|
+
try {
|
|
430
|
+
return await PrismaService.main.user.findUnique({
|
|
431
|
+
where: { email },
|
|
432
|
+
});
|
|
433
|
+
} catch (error) {
|
|
434
|
+
console.error('[UserRepository] Error finding user by email:', error);
|
|
435
|
+
throw new Error(`Failed to find user with email: ${email}`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Create new user
|
|
441
|
+
*/
|
|
442
|
+
async create(data: Prisma.UserCreateInput): Promise<User> {
|
|
443
|
+
try {
|
|
444
|
+
return await PrismaService.main.user.create({ data });
|
|
445
|
+
} catch (error) {
|
|
446
|
+
console.error('[UserRepository] Error creating user:', error);
|
|
447
|
+
throw new Error('Failed to create user');
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Update user
|
|
453
|
+
*/
|
|
454
|
+
async update(userId: string, data: Prisma.UserUpdateInput): Promise<User> {
|
|
455
|
+
try {
|
|
456
|
+
return await PrismaService.main.user.update({
|
|
457
|
+
where: { userID: userId },
|
|
458
|
+
data,
|
|
459
|
+
});
|
|
460
|
+
} catch (error) {
|
|
461
|
+
console.error('[UserRepository] Error updating user:', error);
|
|
462
|
+
throw new Error(`Failed to update user: ${userId}`);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Delete user (soft delete by setting isActive = false)
|
|
468
|
+
*/
|
|
469
|
+
async delete(userId: string): Promise<User> {
|
|
470
|
+
try {
|
|
471
|
+
return await PrismaService.main.user.update({
|
|
472
|
+
where: { userID: userId },
|
|
473
|
+
data: { isActive: false },
|
|
474
|
+
});
|
|
475
|
+
} catch (error) {
|
|
476
|
+
console.error('[UserRepository] Error deleting user:', error);
|
|
477
|
+
throw new Error(`Failed to delete user: ${userId}`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Check if email exists
|
|
483
|
+
*/
|
|
484
|
+
async emailExists(email: string): Promise<boolean> {
|
|
485
|
+
try {
|
|
486
|
+
const count = await PrismaService.main.user.count({
|
|
487
|
+
where: { email },
|
|
488
|
+
});
|
|
489
|
+
return count > 0;
|
|
490
|
+
} catch (error) {
|
|
491
|
+
console.error('[UserRepository] Error checking email exists:', error);
|
|
492
|
+
throw new Error('Failed to check if email exists');
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Export singleton instance
|
|
498
|
+
export const userRepository = new UserRepository();
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
**Using Repository in Service:**
|
|
502
|
+
|
|
503
|
+
```typescript
|
|
504
|
+
// services/userService.ts
|
|
505
|
+
import { userRepository } from '../repositories/UserRepository';
|
|
506
|
+
import { ConflictError, NotFoundError } from '../utils/errors';
|
|
507
|
+
|
|
508
|
+
export class UserService {
|
|
509
|
+
/**
|
|
510
|
+
* Create new user with business rules
|
|
511
|
+
*/
|
|
512
|
+
async createUser(data: { email: string; name: string; roles: string[] }): Promise<User> {
|
|
513
|
+
// Business rule: Check if email already exists
|
|
514
|
+
const emailExists = await userRepository.emailExists(data.email);
|
|
515
|
+
if (emailExists) {
|
|
516
|
+
throw new ConflictError('Email already exists');
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Business rule: Validate roles
|
|
520
|
+
const validRoles = ['admin', 'operations', 'user'];
|
|
521
|
+
const invalidRoles = data.roles.filter((role) => !validRoles.includes(role));
|
|
522
|
+
if (invalidRoles.length > 0) {
|
|
523
|
+
throw new ValidationError(`Invalid roles: ${invalidRoles.join(', ')}`);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Create user via repository
|
|
527
|
+
return await userRepository.create({
|
|
528
|
+
email: data.email,
|
|
529
|
+
name: data.name,
|
|
530
|
+
roles: data.roles,
|
|
531
|
+
isActive: true,
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Get user by ID
|
|
537
|
+
*/
|
|
538
|
+
async getUser(userId: string): Promise<User> {
|
|
539
|
+
const user = await userRepository.findById(userId);
|
|
540
|
+
|
|
541
|
+
if (!user) {
|
|
542
|
+
throw new NotFoundError(`User not found: ${userId}`);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return user;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
## Service Design Principles
|
|
553
|
+
|
|
554
|
+
### 1. Single Responsibility
|
|
555
|
+
|
|
556
|
+
Each service should have ONE clear purpose:
|
|
557
|
+
|
|
558
|
+
```typescript
|
|
559
|
+
// ✅ GOOD - Single responsibility
|
|
560
|
+
class UserService {
|
|
561
|
+
async createUser() {}
|
|
562
|
+
async updateUser() {}
|
|
563
|
+
async deleteUser() {}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
class EmailService {
|
|
567
|
+
async sendEmail() {}
|
|
568
|
+
async sendBulkEmails() {}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// ❌ BAD - Too many responsibilities
|
|
572
|
+
class UserService {
|
|
573
|
+
async createUser() {}
|
|
574
|
+
async sendWelcomeEmail() {} // Should be EmailService
|
|
575
|
+
async logUserActivity() {} // Should be AuditService
|
|
576
|
+
async processPayment() {} // Should be PaymentService
|
|
577
|
+
}
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
### 2. Clear Method Names
|
|
581
|
+
|
|
582
|
+
Method names should describe WHAT they do:
|
|
583
|
+
|
|
584
|
+
```typescript
|
|
585
|
+
// ✅ GOOD - Clear intent
|
|
586
|
+
async createNotification()
|
|
587
|
+
async getUserPreferences()
|
|
588
|
+
async shouldBatchEmail()
|
|
589
|
+
async routeNotification()
|
|
590
|
+
|
|
591
|
+
// ❌ BAD - Vague or misleading
|
|
592
|
+
async process()
|
|
593
|
+
async handle()
|
|
594
|
+
async doIt()
|
|
595
|
+
async execute()
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### 3. Return Types
|
|
599
|
+
|
|
600
|
+
Always use explicit return types:
|
|
601
|
+
|
|
602
|
+
```typescript
|
|
603
|
+
// ✅ GOOD - Explicit types
|
|
604
|
+
async createUser(data: CreateUserDTO): Promise<User> {}
|
|
605
|
+
async findUsers(): Promise<User[]> {}
|
|
606
|
+
async deleteUser(id: string): Promise<void> {}
|
|
607
|
+
|
|
608
|
+
// ❌ BAD - Implicit any
|
|
609
|
+
async createUser(data) {} // No types!
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
### 4. Error Handling
|
|
613
|
+
|
|
614
|
+
Services should throw meaningful errors:
|
|
615
|
+
|
|
616
|
+
```typescript
|
|
617
|
+
// ✅ GOOD - Meaningful errors
|
|
618
|
+
if (!user) {
|
|
619
|
+
throw new NotFoundError(`User not found: ${userId}`);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
if (emailExists) {
|
|
623
|
+
throw new ConflictError('Email already exists');
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// ❌ BAD - Generic errors
|
|
627
|
+
if (!user) {
|
|
628
|
+
throw new Error('Error'); // What error?
|
|
629
|
+
}
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
### 5. Avoid God Services
|
|
633
|
+
|
|
634
|
+
Don't create services that do everything:
|
|
635
|
+
|
|
636
|
+
```typescript
|
|
637
|
+
// ❌ BAD - God service
|
|
638
|
+
class WorkflowService {
|
|
639
|
+
async startWorkflow() {}
|
|
640
|
+
async completeStep() {}
|
|
641
|
+
async assignRoles() {}
|
|
642
|
+
async sendNotifications() {} // Should be NotificationService
|
|
643
|
+
async validatePermissions() {} // Should be PermissionService
|
|
644
|
+
async logAuditTrail() {} // Should be AuditService
|
|
645
|
+
// ... 50 more methods
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// ✅ GOOD - Focused services
|
|
649
|
+
class WorkflowService {
|
|
650
|
+
constructor(
|
|
651
|
+
private notificationService: NotificationService,
|
|
652
|
+
private permissionService: PermissionService,
|
|
653
|
+
private auditService: AuditService
|
|
654
|
+
) {}
|
|
655
|
+
|
|
656
|
+
async startWorkflow() {
|
|
657
|
+
// Orchestrate other services
|
|
658
|
+
await this.permissionService.checkPermission();
|
|
659
|
+
await this.workflowRepository.create();
|
|
660
|
+
await this.notificationService.notify();
|
|
661
|
+
await this.auditService.log();
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
---
|
|
667
|
+
|
|
668
|
+
## Caching Strategies
|
|
669
|
+
|
|
670
|
+
### 1. In-Memory Caching
|
|
671
|
+
|
|
672
|
+
```typescript
|
|
673
|
+
class UserService {
|
|
674
|
+
private cache: Map<string, { user: User; timestamp: number }> = new Map();
|
|
675
|
+
private CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
676
|
+
|
|
677
|
+
async getUser(userId: string): Promise<User> {
|
|
678
|
+
// Check cache
|
|
679
|
+
const cached = this.cache.get(userId);
|
|
680
|
+
if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) {
|
|
681
|
+
return cached.user;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Fetch from database
|
|
685
|
+
const user = await userRepository.findById(userId);
|
|
686
|
+
|
|
687
|
+
// Update cache
|
|
688
|
+
if (user) {
|
|
689
|
+
this.cache.set(userId, { user, timestamp: Date.now() });
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
return user;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
clearUserCache(userId: string): void {
|
|
696
|
+
this.cache.delete(userId);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
### 2. Cache Invalidation
|
|
702
|
+
|
|
703
|
+
```typescript
|
|
704
|
+
class UserService {
|
|
705
|
+
async updateUser(userId: string, data: UpdateUserDTO): Promise<User> {
|
|
706
|
+
// Update in database
|
|
707
|
+
const user = await userRepository.update(userId, data);
|
|
708
|
+
|
|
709
|
+
// Invalidate cache
|
|
710
|
+
this.clearUserCache(userId);
|
|
711
|
+
|
|
712
|
+
return user;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
---
|
|
718
|
+
|
|
719
|
+
## Testing Services
|
|
720
|
+
|
|
721
|
+
### Unit Tests
|
|
722
|
+
|
|
723
|
+
```typescript
|
|
724
|
+
// tests/userService.test.ts
|
|
725
|
+
import { UserService } from '../services/userService';
|
|
726
|
+
import { userRepository } from '../repositories/UserRepository';
|
|
727
|
+
import { ConflictError } from '../utils/errors';
|
|
728
|
+
|
|
729
|
+
// Mock repository
|
|
730
|
+
jest.mock('../repositories/UserRepository');
|
|
731
|
+
|
|
732
|
+
describe('UserService', () => {
|
|
733
|
+
let userService: UserService;
|
|
734
|
+
|
|
735
|
+
beforeEach(() => {
|
|
736
|
+
userService = new UserService();
|
|
737
|
+
jest.clearAllMocks();
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
describe('createUser', () => {
|
|
741
|
+
it('should create user when email does not exist', async () => {
|
|
742
|
+
// Arrange
|
|
743
|
+
const userData = {
|
|
744
|
+
email: 'test@example.com',
|
|
745
|
+
name: 'Test User',
|
|
746
|
+
roles: ['user'],
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
(userRepository.emailExists as jest.Mock).mockResolvedValue(false);
|
|
750
|
+
(userRepository.create as jest.Mock).mockResolvedValue({
|
|
751
|
+
userID: '123',
|
|
752
|
+
...userData,
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
// Act
|
|
756
|
+
const user = await userService.createUser(userData);
|
|
757
|
+
|
|
758
|
+
// Assert
|
|
759
|
+
expect(user).toBeDefined();
|
|
760
|
+
expect(user.email).toBe(userData.email);
|
|
761
|
+
expect(userRepository.emailExists).toHaveBeenCalledWith(userData.email);
|
|
762
|
+
expect(userRepository.create).toHaveBeenCalled();
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
it('should throw ConflictError when email exists', async () => {
|
|
766
|
+
// Arrange
|
|
767
|
+
const userData = {
|
|
768
|
+
email: 'existing@example.com',
|
|
769
|
+
name: 'Test User',
|
|
770
|
+
roles: ['user'],
|
|
771
|
+
};
|
|
772
|
+
|
|
773
|
+
(userRepository.emailExists as jest.Mock).mockResolvedValue(true);
|
|
774
|
+
|
|
775
|
+
// Act & Assert
|
|
776
|
+
await expect(userService.createUser(userData)).rejects.toThrow(ConflictError);
|
|
777
|
+
expect(userRepository.create).not.toHaveBeenCalled();
|
|
778
|
+
});
|
|
779
|
+
});
|
|
780
|
+
});
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
---
|
|
784
|
+
|
|
785
|
+
**Related Files:**
|
|
786
|
+
- [SKILL.md](SKILL.md) - Main guide
|
|
787
|
+
- [routing-and-controllers.md](routing-and-controllers.md) - Controllers that use services
|
|
788
|
+
- [database-patterns.md](database-patterns.md) - Prisma and repository patterns
|
|
789
|
+
- [complete-examples.md](complete-examples.md) - Full service/repository examples
|