moai-adk 0.4.5__py3-none-any.whl → 0.20.1__py3-none-any.whl
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.
Potentially problematic release.
This version of moai-adk might be problematic. Click here for more details.
- moai_adk/__init__.py +1 -1
- moai_adk/__main__.py +74 -1
- moai_adk/cli/commands/__init__.py +1 -1
- moai_adk/cli/commands/analyze.py +119 -0
- moai_adk/cli/commands/backup.py +25 -1
- moai_adk/cli/commands/doctor.py +31 -5
- moai_adk/cli/commands/improve_user_experience.py +307 -0
- moai_adk/cli/commands/init.py +111 -10
- moai_adk/cli/commands/status.py +33 -3
- moai_adk/cli/commands/update.py +921 -130
- moai_adk/cli/commands/validate_links.py +120 -0
- moai_adk/cli/prompts/init_prompts.py +22 -87
- moai_adk/core/analysis/__init__.py +9 -0
- moai_adk/core/analysis/session_analyzer.py +388 -0
- moai_adk/core/analysis/tag_chain_analyzer.py +344 -0
- moai_adk/core/analysis/tag_chain_repair.py +879 -0
- moai_adk/core/config/__init__.py +19 -0
- moai_adk/core/config/migration.py +235 -0
- moai_adk/core/git/__init__.py +1 -1
- moai_adk/core/git/branch.py +1 -1
- moai_adk/core/git/commit.py +1 -1
- moai_adk/core/git/manager.py +1 -1
- moai_adk/core/issue_creator.py +313 -0
- moai_adk/core/mcp/setup.py +56 -0
- moai_adk/core/mcp/setup_old.py +296 -0
- moai_adk/core/project/backup_utils.py +1 -1
- moai_adk/core/project/checker.py +2 -2
- moai_adk/core/project/detector.py +211 -12
- moai_adk/core/project/initializer.py +85 -15
- moai_adk/core/project/phase_executor.py +76 -13
- moai_adk/core/project/validator.py +13 -13
- moai_adk/core/quality/__init__.py +1 -1
- moai_adk/core/quality/trust_checker.py +1 -1
- moai_adk/core/quality/validators/__init__.py +1 -1
- moai_adk/core/quality/validators/base_validator.py +1 -1
- moai_adk/core/tags/__init__.py +86 -0
- moai_adk/core/tags/auto_corrector.py +693 -0
- moai_adk/core/tags/ci_validator.py +463 -0
- moai_adk/core/tags/cli.py +283 -0
- moai_adk/core/tags/generator.py +109 -0
- moai_adk/core/tags/inserter.py +99 -0
- moai_adk/core/tags/mapper.py +126 -0
- moai_adk/core/tags/parser.py +76 -0
- moai_adk/core/tags/policy_validator.py +580 -0
- moai_adk/core/tags/pre_commit_validator.py +421 -0
- moai_adk/core/tags/reporter.py +956 -0
- moai_adk/core/tags/rollback_manager.py +525 -0
- moai_adk/core/tags/tags.py +149 -0
- moai_adk/core/tags/validator.py +897 -0
- moai_adk/core/template/__init__.py +1 -1
- moai_adk/core/template/backup.py +1 -1
- moai_adk/core/template/merger.py +50 -1
- moai_adk/core/template/processor.py +119 -13
- moai_adk/core/template_engine.py +268 -0
- moai_adk/templates/.claude/agents/alfred/backend-expert.md +348 -0
- moai_adk/templates/.claude/agents/alfred/cc-manager.md +209 -944
- moai_adk/templates/.claude/agents/alfred/database-expert.md +352 -0
- moai_adk/templates/.claude/agents/alfred/debug-helper.md +34 -5
- moai_adk/templates/.claude/agents/alfred/devops-expert.md +464 -0
- moai_adk/templates/.claude/agents/alfred/doc-syncer.md +38 -8
- moai_adk/templates/.claude/agents/alfred/format-expert.md +469 -0
- moai_adk/templates/.claude/agents/alfred/frontend-expert.md +357 -0
- moai_adk/templates/.claude/agents/alfred/git-manager.md +128 -9
- moai_adk/templates/.claude/agents/alfred/implementation-planner.md +104 -6
- moai_adk/templates/.claude/agents/alfred/project-manager.md +88 -16
- moai_adk/templates/.claude/agents/alfred/quality-gate.md +36 -9
- moai_adk/templates/.claude/agents/alfred/security-expert.md +270 -0
- moai_adk/templates/.claude/agents/alfred/skill-factory.md +865 -0
- moai_adk/templates/.claude/agents/alfred/spec-builder.md +214 -43
- moai_adk/templates/.claude/agents/alfred/tag-agent.md +111 -9
- moai_adk/templates/.claude/agents/alfred/tdd-implementer.md +309 -160
- moai_adk/templates/.claude/agents/alfred/trust-checker.md +36 -7
- moai_adk/templates/.claude/agents/alfred/ui-ux-expert.md +605 -0
- moai_adk/templates/.claude/commands/alfred/0-project.md +393 -966
- moai_adk/templates/.claude/commands/alfred/1-plan.md +651 -367
- moai_adk/templates/.claude/commands/alfred/2-run.md +388 -241
- moai_adk/templates/.claude/commands/alfred/3-sync.md +1921 -410
- moai_adk/templates/.claude/commands/alfred/9-feedback.md +153 -0
- moai_adk/templates/.claude/commands/alfred/release-new.md +3604 -0
- moai_adk/templates/.claude/hooks/alfred/core/project.py +484 -20
- moai_adk/templates/.claude/hooks/alfred/core/timeout.py +136 -0
- moai_adk/templates/.claude/hooks/alfred/core/ttl_cache.py +108 -0
- moai_adk/templates/.claude/hooks/alfred/core/version_cache.py +198 -0
- moai_adk/templates/.claude/hooks/alfred/handlers/__init__.py +14 -6
- moai_adk/templates/.claude/hooks/alfred/post_tool__enable_streaming_ui.py +50 -0
- moai_adk/templates/.claude/hooks/alfred/post_tool__log_changes.py +93 -0
- moai_adk/templates/.claude/hooks/alfred/post_tool__tag_auto_corrector.py +407 -0
- moai_adk/templates/.claude/hooks/alfred/pre_tool__auto_checkpoint.py +99 -0
- moai_adk/templates/.claude/hooks/alfred/pre_tool__realtime_tag_monitor.py +335 -0
- moai_adk/templates/.claude/hooks/alfred/pre_tool__tag_policy_validator.py +325 -0
- moai_adk/templates/.claude/hooks/alfred/session_end__cleanup.py +93 -0
- moai_adk/templates/.claude/hooks/alfred/session_start__auto_cleanup.py +580 -0
- moai_adk/templates/.claude/hooks/alfred/session_start__show_project_info.py +298 -0
- moai_adk/templates/.claude/hooks/alfred/shared/core/__init__.py +170 -0
- moai_adk/templates/.claude/hooks/alfred/{core → shared/core}/checkpoint.py +3 -3
- moai_adk/templates/.claude/hooks/alfred/{core → shared/core}/context.py +5 -5
- moai_adk/templates/.claude/hooks/alfred/shared/core/project.py +749 -0
- moai_adk/templates/.claude/hooks/alfred/shared/core/tags.py +230 -0
- moai_adk/templates/.claude/hooks/alfred/shared/core/version_cache.py +198 -0
- moai_adk/templates/.claude/hooks/alfred/shared/handlers/__init__.py +21 -0
- moai_adk/templates/.claude/hooks/alfred/shared/handlers/daily_analysis.py +351 -0
- moai_adk/templates/.claude/hooks/alfred/shared/handlers/notification.py +154 -0
- moai_adk/templates/.claude/hooks/alfred/shared/handlers/session.py +174 -0
- moai_adk/templates/.claude/hooks/alfred/shared/handlers/tool.py +87 -0
- moai_adk/templates/.claude/hooks/alfred/shared/handlers/user.py +61 -0
- moai_adk/templates/.claude/hooks/alfred/user_prompt__jit_load_docs.py +111 -0
- moai_adk/templates/.claude/hooks/alfred/utils/__init__.py +1 -0
- moai_adk/templates/.claude/hooks/alfred/utils/hook_config.py +94 -0
- moai_adk/templates/.claude/hooks/alfred/utils/timeout.py +161 -0
- moai_adk/templates/.claude/output-styles/alfred/alfred-moai-adk-beginner.md +267 -0
- moai_adk/templates/.claude/output-styles/alfred/keating-personal-tutor.md +440 -0
- moai_adk/templates/.claude/output-styles/alfred/r2d2-agentic-coding.md +583 -0
- moai_adk/templates/.claude/settings.json +96 -14
- moai_adk/templates/.claude/skills/moai-alfred-agent-guide/SKILL.md +70 -0
- moai_adk/templates/.claude/skills/moai-alfred-agent-guide/examples.md +62 -0
- moai_adk/templates/.claude/skills/moai-alfred-agent-guide/reference.md +242 -0
- moai_adk/templates/.claude/skills/moai-alfred-ask-user-questions/SKILL.md +237 -0
- moai_adk/templates/.claude/skills/moai-alfred-ask-user-questions/examples.md +871 -0
- moai_adk/templates/.claude/skills/moai-alfred-ask-user-questions/reference.md +653 -0
- moai_adk/templates/.claude/skills/moai-alfred-clone-pattern/README.md +162 -0
- moai_adk/templates/.claude/skills/moai-alfred-clone-pattern/SKILL.md +227 -0
- moai_adk/templates/.claude/skills/moai-alfred-clone-pattern/examples.md +354 -0
- moai_adk/templates/.claude/skills/moai-alfred-clone-pattern/reference.md +158 -0
- moai_adk/templates/.claude/skills/moai-alfred-code-reviewer/SKILL.md +179 -79
- moai_adk/templates/.claude/skills/moai-alfred-code-reviewer/examples.md +117 -0
- moai_adk/templates/.claude/skills/moai-alfred-code-reviewer/scripts/pre-review-check.sh +62 -0
- moai_adk/templates/.claude/skills/moai-alfred-config-schema/SKILL.md +132 -0
- moai_adk/templates/.claude/skills/moai-alfred-config-schema/examples.md +28 -0
- moai_adk/templates/.claude/skills/moai-alfred-config-schema/reference.md +444 -0
- moai_adk/templates/.claude/skills/moai-alfred-context-budget/SKILL.md +62 -0
- moai_adk/templates/.claude/skills/moai-alfred-context-budget/examples.md +28 -0
- moai_adk/templates/.claude/skills/moai-alfred-context-budget/reference.md +405 -0
- moai_adk/templates/.claude/skills/moai-alfred-dev-guide/SKILL.md +51 -0
- moai_adk/templates/.claude/skills/moai-alfred-dev-guide/examples.md +355 -0
- moai_adk/templates/.claude/skills/moai-alfred-dev-guide/reference.md +239 -0
- moai_adk/templates/.claude/skills/moai-alfred-expertise-detection/SKILL.md +323 -0
- moai_adk/templates/.claude/skills/moai-alfred-expertise-detection/examples.md +286 -0
- moai_adk/templates/.claude/skills/moai-alfred-expertise-detection/reference.md +126 -0
- moai_adk/templates/.claude/skills/moai-alfred-issue-labels/SKILL.md +229 -0
- moai_adk/templates/.claude/skills/moai-alfred-issue-labels/examples.md +4 -0
- moai_adk/templates/.claude/skills/moai-alfred-issue-labels/reference.md +150 -0
- moai_adk/templates/.claude/skills/moai-alfred-language-detection/SKILL.md +87 -73
- moai_adk/templates/.claude/skills/moai-alfred-language-detection/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-alfred-language-detection/reference.md +28 -0
- moai_adk/templates/.claude/skills/moai-alfred-personas/README.md +42 -0
- moai_adk/templates/.claude/skills/moai-alfred-personas/SKILL.md +429 -0
- moai_adk/templates/.claude/skills/moai-alfred-personas/examples.md +520 -0
- moai_adk/templates/.claude/skills/moai-alfred-personas/reference.md +405 -0
- moai_adk/templates/.claude/skills/moai-alfred-practices/SKILL.md +89 -0
- moai_adk/templates/.claude/skills/moai-alfred-practices/examples.md +122 -0
- moai_adk/templates/.claude/skills/moai-alfred-practices/reference.md +369 -0
- moai_adk/templates/.claude/skills/moai-alfred-proactive-suggestions/SKILL.md +508 -0
- moai_adk/templates/.claude/skills/moai-alfred-proactive-suggestions/examples.md +481 -0
- moai_adk/templates/.claude/skills/moai-alfred-proactive-suggestions/reference.md +100 -0
- moai_adk/templates/.claude/skills/moai-alfred-rules/SKILL.md +77 -0
- moai_adk/templates/.claude/skills/moai-alfred-rules/examples.md +265 -0
- moai_adk/templates/.claude/skills/moai-alfred-rules/reference.md +539 -0
- moai_adk/templates/.claude/skills/moai-alfred-session-state/SKILL.md +320 -0
- moai_adk/templates/.claude/skills/moai-alfred-session-state/examples.md +4 -0
- moai_adk/templates/.claude/skills/moai-alfred-session-state/reference.md +84 -0
- moai_adk/templates/.claude/skills/moai-alfred-spec-authoring/README.md +137 -0
- moai_adk/templates/.claude/skills/moai-alfred-spec-authoring/SKILL.md +219 -0
- moai_adk/templates/.claude/skills/moai-alfred-spec-authoring/examples/validate-spec.sh +161 -0
- moai_adk/templates/.claude/skills/moai-alfred-spec-authoring/examples.md +541 -0
- moai_adk/templates/.claude/skills/moai-alfred-spec-authoring/reference.md +622 -0
- moai_adk/templates/.claude/skills/moai-alfred-todowrite-pattern/SKILL.md +19 -0
- moai_adk/templates/.claude/skills/moai-alfred-todowrite-pattern/examples.md +4 -0
- moai_adk/templates/.claude/skills/moai-alfred-todowrite-pattern/reference.md +211 -0
- moai_adk/templates/.claude/skills/moai-alfred-workflow/SKILL.md +288 -0
- moai_adk/templates/.claude/skills/moai-cc-agents/SKILL.md +269 -0
- moai_adk/templates/.claude/skills/moai-cc-agents/templates/agent-template.md +32 -0
- moai_adk/templates/.claude/skills/moai-cc-claude-md/SKILL.md +298 -0
- moai_adk/templates/.claude/skills/moai-cc-claude-md/templates/CLAUDE-template.md +26 -0
- moai_adk/templates/.claude/skills/moai-cc-commands/SKILL.md +307 -0
- moai_adk/templates/.claude/skills/moai-cc-commands/templates/command-template.md +21 -0
- moai_adk/templates/.claude/skills/moai-cc-hooks/SKILL.md +252 -0
- moai_adk/templates/.claude/skills/moai-cc-hooks/scripts/pre-bash-check.sh +19 -0
- moai_adk/templates/.claude/skills/moai-cc-hooks/scripts/preserve-permissions.sh +19 -0
- moai_adk/templates/.claude/skills/moai-cc-hooks/scripts/validate-bash-command.py +24 -0
- moai_adk/templates/.claude/skills/moai-cc-mcp-plugins/SKILL.md +199 -0
- moai_adk/templates/.claude/skills/moai-cc-mcp-plugins/templates/settings-mcp-template.json +39 -0
- moai_adk/templates/.claude/skills/moai-cc-memory/SKILL.md +316 -0
- moai_adk/templates/.claude/skills/moai-cc-memory/templates/session-summary-template.md +18 -0
- moai_adk/templates/.claude/skills/moai-cc-settings/SKILL.md +263 -0
- moai_adk/templates/.claude/skills/moai-cc-settings/templates/settings-complete-template.json +30 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/CHECKLIST.md +482 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/EXAMPLES.md +303 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/INTERACTIVE-DISCOVERY.md +524 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/METADATA.md +477 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/PARALLEL-ANALYSIS-REPORT.md +429 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/PYTHON-VERSION-MATRIX.md +391 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/SKILL-FACTORY-WORKFLOW.md +431 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/SKILL-UPDATE-ADVISOR.md +577 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/SKILL.md +273 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/STEP-BY-STEP-GUIDE.md +466 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/STRUCTURE.md +583 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/WEB-RESEARCH.md +526 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/reference.md +608 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/scripts/generate-structure.sh +328 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/scripts/validate-skill.sh +312 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/templates/SKILL_TEMPLATE.md +245 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/templates/examples-template.md +285 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/templates/reference-template.md +278 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/templates/scripts-template.sh +303 -0
- moai_adk/templates/.claude/skills/moai-cc-skills/SKILL.md +291 -0
- moai_adk/templates/.claude/skills/moai-cc-skills/templates/SKILL-template.md +15 -0
- moai_adk/templates/.claude/skills/moai-change-logger/SKILL.md +563 -0
- moai_adk/templates/.claude/skills/moai-design-systems/SKILL.md +802 -0
- moai_adk/templates/.claude/skills/moai-design-systems/examples.md +1238 -0
- moai_adk/templates/.claude/skills/moai-design-systems/reference.md +673 -0
- moai_adk/templates/.claude/skills/moai-domain-backend/SKILL.md +234 -43
- moai_adk/templates/.claude/skills/moai-domain-backend/examples.md +1633 -0
- moai_adk/templates/.claude/skills/moai-domain-backend/reference.md +660 -0
- moai_adk/templates/.claude/skills/moai-domain-cli-tool/SKILL.md +97 -69
- moai_adk/templates/.claude/skills/moai-domain-cli-tool/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-domain-cli-tool/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-domain-data-science/SKILL.md +97 -72
- moai_adk/templates/.claude/skills/moai-domain-data-science/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-domain-data-science/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-domain-database/SKILL.md +97 -74
- moai_adk/templates/.claude/skills/moai-domain-database/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-domain-database/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-domain-devops/SKILL.md +98 -74
- moai_adk/templates/.claude/skills/moai-domain-devops/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-domain-devops/reference.md +31 -0
- moai_adk/templates/.claude/skills/moai-domain-frontend/SKILL.md +102 -73
- moai_adk/templates/.claude/skills/moai-domain-frontend/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-domain-frontend/reference.md +31 -0
- moai_adk/templates/.claude/skills/moai-domain-ml/SKILL.md +97 -73
- moai_adk/templates/.claude/skills/moai-domain-ml/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-domain-ml/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-domain-mobile-app/SKILL.md +97 -67
- moai_adk/templates/.claude/skills/moai-domain-mobile-app/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-domain-mobile-app/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-domain-security/SKILL.md +97 -79
- moai_adk/templates/.claude/skills/moai-domain-security/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-domain-security/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-domain-web-api/SKILL.md +97 -71
- moai_adk/templates/.claude/skills/moai-domain-web-api/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-domain-web-api/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-essentials-debug/SKILL.md +265 -64
- moai_adk/templates/.claude/skills/moai-essentials-debug/examples.md +1064 -0
- moai_adk/templates/.claude/skills/moai-essentials-debug/reference.md +1047 -0
- moai_adk/templates/.claude/skills/moai-essentials-perf/SKILL.md +87 -78
- moai_adk/templates/.claude/skills/moai-essentials-perf/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-essentials-perf/reference.md +28 -0
- moai_adk/templates/.claude/skills/moai-essentials-refactor/SKILL.md +87 -70
- moai_adk/templates/.claude/skills/moai-essentials-refactor/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-essentials-refactor/reference.md +28 -0
- moai_adk/templates/.claude/skills/moai-essentials-review/SKILL.md +87 -86
- moai_adk/templates/.claude/skills/moai-essentials-review/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-essentials-review/reference.md +28 -0
- moai_adk/templates/.claude/skills/moai-foundation-ears/SKILL.md +80 -62
- moai_adk/templates/.claude/skills/moai-foundation-ears/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-foundation-ears/reference.md +28 -0
- moai_adk/templates/.claude/skills/moai-foundation-git/SKILL.md +207 -50
- moai_adk/templates/.claude/skills/moai-foundation-git/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-foundation-git/reference.md +29 -0
- moai_adk/templates/.claude/skills/moai-foundation-langs/SKILL.md +90 -71
- moai_adk/templates/.claude/skills/moai-foundation-langs/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-foundation-langs/reference.md +28 -0
- moai_adk/templates/.claude/skills/moai-foundation-specs/SKILL.md +78 -58
- moai_adk/templates/.claude/skills/moai-foundation-specs/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-foundation-specs/reference.md +28 -0
- moai_adk/templates/.claude/skills/moai-foundation-tags/SKILL.md +78 -51
- moai_adk/templates/.claude/skills/moai-foundation-tags/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-foundation-tags/reference.md +28 -0
- moai_adk/templates/.claude/skills/moai-foundation-trust/.!11330!examples.md +0 -0
- moai_adk/templates/.claude/skills/moai-foundation-trust/SKILL.md +253 -32
- moai_adk/templates/.claude/skills/moai-foundation-trust/examples.md +0 -0
- moai_adk/templates/.claude/skills/moai-foundation-trust/reference.md +1099 -0
- moai_adk/templates/.claude/skills/moai-jit-docs-enhanced/SKILL.md +460 -0
- moai_adk/templates/.claude/skills/moai-lang-c/SKILL.md +98 -74
- moai_adk/templates/.claude/skills/moai-lang-c/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-c/reference.md +31 -0
- moai_adk/templates/.claude/skills/moai-lang-cpp/SKILL.md +98 -76
- moai_adk/templates/.claude/skills/moai-lang-cpp/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-cpp/reference.md +31 -0
- moai_adk/templates/.claude/skills/moai-lang-csharp/SKILL.md +2358 -70
- moai_adk/templates/.claude/skills/moai-lang-csharp/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-csharp/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-lang-dart/SKILL.md +2962 -68
- moai_adk/templates/.claude/skills/moai-lang-dart/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-dart/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-lang-go/SKILL.md +1898 -70
- moai_adk/templates/.claude/skills/moai-lang-go/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-go/reference.md +31 -0
- moai_adk/templates/.claude/skills/moai-lang-java/SKILL.md +1465 -68
- moai_adk/templates/.claude/skills/moai-lang-java/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-java/reference.md +31 -0
- moai_adk/templates/.claude/skills/moai-lang-javascript/SKILL.md +2364 -66
- moai_adk/templates/.claude/skills/moai-lang-javascript/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-javascript/reference.md +32 -0
- moai_adk/templates/.claude/skills/moai-lang-kotlin/SKILL.md +1630 -69
- moai_adk/templates/.claude/skills/moai-lang-kotlin/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-kotlin/reference.md +31 -0
- moai_adk/templates/.claude/skills/moai-lang-php/SKILL.md +89 -61
- moai_adk/templates/.claude/skills/moai-lang-php/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-php/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-lang-python/SKILL.md +735 -66
- moai_adk/templates/.claude/skills/moai-lang-python/examples.md +624 -0
- moai_adk/templates/.claude/skills/moai-lang-python/reference.md +316 -0
- moai_adk/templates/.claude/skills/moai-lang-r/SKILL.md +97 -73
- moai_adk/templates/.claude/skills/moai-lang-r/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-r/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-lang-ruby/SKILL.md +98 -73
- moai_adk/templates/.claude/skills/moai-lang-ruby/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-ruby/reference.md +31 -0
- moai_adk/templates/.claude/skills/moai-lang-rust/SKILL.md +1834 -70
- moai_adk/templates/.claude/skills/moai-lang-rust/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-rust/reference.md +31 -0
- moai_adk/templates/.claude/skills/moai-lang-scala/SKILL.md +99 -74
- moai_adk/templates/.claude/skills/moai-lang-scala/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-scala/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-lang-shell/SKILL.md +97 -74
- moai_adk/templates/.claude/skills/moai-lang-shell/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-shell/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-lang-sql/SKILL.md +98 -74
- moai_adk/templates/.claude/skills/moai-lang-sql/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-sql/reference.md +31 -0
- moai_adk/templates/.claude/skills/moai-lang-swift/SKILL.md +1959 -69
- moai_adk/templates/.claude/skills/moai-lang-swift/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-swift/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-lang-template/SKILL.md +348 -0
- moai_adk/templates/.claude/skills/moai-lang-template/VARIABLES.md +98 -0
- moai_adk/templates/.claude/skills/moai-lang-typescript/SKILL.md +1230 -66
- moai_adk/templates/.claude/skills/moai-lang-typescript/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-typescript/reference.md +34 -0
- moai_adk/templates/.claude/skills/moai-learning-optimizer/SKILL.md +575 -0
- moai_adk/templates/.claude/skills/moai-project-batch-questions/README.md +50 -0
- moai_adk/templates/.claude/skills/moai-project-batch-questions/SKILL.md +304 -0
- moai_adk/templates/.claude/skills/moai-project-batch-questions/examples.md +417 -0
- moai_adk/templates/.claude/skills/moai-project-batch-questions/reference.md +704 -0
- moai_adk/templates/.claude/skills/moai-project-config-manager/README.md +87 -0
- moai_adk/templates/.claude/skills/moai-project-config-manager/SKILL.md +552 -0
- moai_adk/templates/.claude/skills/moai-project-config-manager/examples.md +1109 -0
- moai_adk/templates/.claude/skills/moai-project-config-manager/reference.md +514 -0
- moai_adk/templates/.claude/skills/moai-project-config-manager/validate.py +106 -0
- moai_adk/templates/.claude/skills/moai-project-documentation/README.md +11 -0
- moai_adk/templates/.claude/skills/moai-project-documentation/SKILL.md +622 -0
- moai_adk/templates/.claude/skills/moai-project-documentation/examples.md +20 -0
- moai_adk/templates/.claude/skills/moai-project-documentation/reference.md +12 -0
- moai_adk/templates/.claude/skills/moai-project-language-initializer/README.md +152 -0
- moai_adk/templates/.claude/skills/moai-project-language-initializer/SKILL.md +285 -0
- moai_adk/templates/.claude/skills/moai-project-language-initializer/examples.md +333 -0
- moai_adk/templates/.claude/skills/moai-project-language-initializer/reference.md +386 -0
- moai_adk/templates/.claude/skills/moai-project-template-optimizer/README.md +49 -0
- moai_adk/templates/.claude/skills/moai-project-template-optimizer/SKILL.md +319 -0
- moai_adk/templates/.claude/skills/moai-project-template-optimizer/examples.md +58 -0
- moai_adk/templates/.claude/skills/moai-project-template-optimizer/reference.md +123 -0
- moai_adk/templates/.claude/skills/moai-session-info/SKILL.md +314 -0
- moai_adk/templates/.claude/skills/moai-streaming-ui/SKILL.md +552 -0
- moai_adk/templates/.claude/skills/moai-tag-policy-validator/SKILL.md +570 -0
- moai_adk/templates/.git-hooks/pre-commit +66 -0
- moai_adk/templates/.git-hooks/pre-push +255 -0
- moai_adk/templates/.github/workflows/c-tag-validation.yml +11 -0
- moai_adk/templates/.github/workflows/cpp-tag-validation.yml +11 -0
- moai_adk/templates/.github/workflows/csharp-tag-validation.yml +11 -0
- moai_adk/templates/.github/workflows/dart-tag-validation.yml +11 -0
- moai_adk/templates/.github/workflows/go-tag-validation.yml +130 -0
- moai_adk/templates/.github/workflows/java-tag-validation.yml +11 -0
- moai_adk/templates/.github/workflows/javascript-tag-validation.yml +135 -0
- moai_adk/templates/.github/workflows/kotlin-tag-validation.yml +11 -0
- moai_adk/templates/.github/workflows/moai-gitflow.yml +166 -3
- moai_adk/templates/.github/workflows/moai-release-create.yml +100 -0
- moai_adk/templates/.github/workflows/moai-release-pipeline.yml +188 -0
- moai_adk/templates/.github/workflows/php-tag-validation.yml +11 -0
- moai_adk/templates/.github/workflows/python-tag-validation.yml +118 -0
- moai_adk/templates/.github/workflows/release.yml +118 -0
- moai_adk/templates/.github/workflows/ruby-tag-validation.yml +11 -0
- moai_adk/templates/.github/workflows/rust-tag-validation.yml +11 -0
- moai_adk/templates/.github/workflows/shell-tag-validation.yml +11 -0
- moai_adk/templates/.github/workflows/spec-issue-sync.yml +338 -0
- moai_adk/templates/.github/workflows/swift-tag-validation.yml +11 -0
- moai_adk/templates/.github/workflows/tag-report.yml +269 -0
- moai_adk/templates/.github/workflows/tag-validation.yml +186 -0
- moai_adk/templates/.github/workflows/typescript-tag-validation.yml +154 -0
- moai_adk/templates/.mcp.json +31 -0
- moai_adk/templates/.moai/config.json +80 -7
- moai_adk/templates/CLAUDE.md +562 -546
- moai_adk/utils/banner.py +5 -5
- moai_adk/utils/common.py +294 -0
- moai_adk/utils/link_validator.py +235 -0
- moai_adk/utils/logger.py +8 -8
- moai_adk/utils/user_experience.py +451 -0
- moai_adk-0.20.1.dist-info/METADATA +233 -0
- moai_adk-0.20.1.dist-info/RECORD +404 -0
- moai_adk/templates/.claude/hooks/alfred/README.md +0 -230
- moai_adk/templates/.claude/hooks/alfred/alfred_hooks.py +0 -156
- moai_adk/templates/.claude/hooks/alfred/core/__init__.py +0 -85
- moai_adk/templates/.claude/hooks/alfred/handlers/notification.py +0 -25
- moai_adk/templates/.claude/hooks/alfred/handlers/session.py +0 -92
- moai_adk/templates/.claude/hooks/alfred/handlers/tool.py +0 -70
- moai_adk/templates/.claude/hooks/alfred/handlers/user.py +0 -41
- moai_adk/templates/.claude/output-styles/alfred/agentic-coding.md +0 -636
- moai_adk/templates/.claude/output-styles/alfred/moai-adk-learning.md +0 -692
- moai_adk/templates/.claude/output-styles/alfred/study-with-alfred.md +0 -470
- moai_adk/templates/.claude/skills/moai-alfred-debugger-pro/SKILL.md +0 -103
- moai_adk/templates/.claude/skills/moai-alfred-ears-authoring/SKILL.md +0 -103
- moai_adk/templates/.claude/skills/moai-alfred-git-workflow/SKILL.md +0 -95
- moai_adk/templates/.claude/skills/moai-alfred-performance-optimizer/SKILL.md +0 -105
- moai_adk/templates/.claude/skills/moai-alfred-refactoring-coach/SKILL.md +0 -97
- moai_adk/templates/.claude/skills/moai-alfred-spec-metadata-validation/SKILL.md +0 -97
- moai_adk/templates/.claude/skills/moai-alfred-tag-scanning/SKILL.md +0 -90
- moai_adk/templates/.claude/skills/moai-alfred-trust-validation/SKILL.md +0 -99
- moai_adk/templates/.claude/skills/moai-alfred-tui-survey/SKILL.md +0 -87
- moai_adk/templates/.claude/skills/moai-alfred-tui-survey/examples.md +0 -62
- moai_adk/templates/.claude/skills/moai-claude-code/SKILL.md +0 -94
- moai_adk/templates/.claude/skills/moai-claude-code/examples.md +0 -513
- moai_adk/templates/.claude/skills/moai-claude-code/reference.md +0 -433
- moai_adk/templates/.claude/skills/moai-claude-code/templates/agent-full.md +0 -332
- moai_adk/templates/.claude/skills/moai-claude-code/templates/command-full.md +0 -384
- moai_adk/templates/.claude/skills/moai-claude-code/templates/plugin-full.json +0 -363
- moai_adk/templates/.claude/skills/moai-claude-code/templates/settings-full.json +0 -595
- moai_adk/templates/.claude/skills/moai-claude-code/templates/skill-full.md +0 -496
- moai_adk/templates/.claude/skills/moai-lang-clojure/SKILL.md +0 -100
- moai_adk/templates/.claude/skills/moai-lang-elixir/SKILL.md +0 -99
- moai_adk/templates/.claude/skills/moai-lang-haskell/SKILL.md +0 -100
- moai_adk/templates/.claude/skills/moai-lang-julia/SKILL.md +0 -98
- moai_adk/templates/.claude/skills/moai-lang-lua/SKILL.md +0 -98
- moai_adk/templates/.github/PULL_REQUEST_TEMPLATE.md +0 -69
- moai_adk/templates/.moai/memory/development-guide.md +0 -344
- moai_adk/templates/.moai/memory/gitflow-protection-policy.md +0 -220
- moai_adk/templates/.moai/memory/spec-metadata.md +0 -356
- moai_adk/templates/.moai/project/product.md +0 -161
- moai_adk/templates/.moai/project/structure.md +0 -156
- moai_adk/templates/.moai/project/tech.md +0 -227
- moai_adk/templates/__init__.py +0 -2
- moai_adk-0.4.5.dist-info/METADATA +0 -369
- moai_adk-0.4.5.dist-info/RECORD +0 -152
- {moai_adk-0.4.5.dist-info → moai_adk-0.20.1.dist-info}/WHEEL +0 -0
- {moai_adk-0.4.5.dist-info → moai_adk-0.20.1.dist-info}/entry_points.txt +0 -0
- {moai_adk-0.4.5.dist-info → moai_adk-0.20.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,100 +1,1928 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
2
|
name: moai-lang-go
|
|
4
|
-
|
|
3
|
+
version: 2.0.0
|
|
4
|
+
created: 2025-11-06
|
|
5
|
+
updated: 2025-11-06
|
|
6
|
+
status: active
|
|
7
|
+
description: "Go best practices with modern cloud-native development, performance optimization, and concurrent programming for 2025"
|
|
8
|
+
keywords: [go, golang, backend, microservices, cloud-native, performance, concurrency, devops]
|
|
5
9
|
allowed-tools:
|
|
6
10
|
- Read
|
|
11
|
+
- Write
|
|
12
|
+
- Edit
|
|
7
13
|
- Bash
|
|
14
|
+
- WebFetch
|
|
15
|
+
- WebSearch
|
|
8
16
|
---
|
|
9
17
|
|
|
10
|
-
# Go
|
|
18
|
+
# Go Development Mastery
|
|
19
|
+
|
|
20
|
+
**Modern Go Development with 2025 Best Practices**
|
|
21
|
+
|
|
22
|
+
> Comprehensive Go development guidance covering cloud-native services, high-performance concurrent programming, microservices architecture, and production-ready applications using the latest tools and methodologies.
|
|
23
|
+
|
|
24
|
+
## What It Does
|
|
11
25
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
26
|
+
- **Cloud-Native Services**: Kubernetes, Docker, and container-native applications
|
|
27
|
+
- **High-Performance APIs**: Fast HTTP servers with minimal memory footprint
|
|
28
|
+
- **Concurrent Programming**: Goroutines, channels, and advanced patterns
|
|
29
|
+
- **Microservices Architecture**: Service discovery, load balancing, distributed systems
|
|
30
|
+
- **Testing & Quality**: Comprehensive testing, benchmarking, and quality assurance
|
|
31
|
+
- **Database Integration**: SQL, NoSQL, caching, and data pipelines
|
|
32
|
+
- **DevOps Integration**: CI/CD, monitoring, observability, and deployment
|
|
33
|
+
- **Enterprise Patterns**: Clean architecture, domain-driven design, scalability
|
|
19
34
|
|
|
20
|
-
##
|
|
35
|
+
## When to Use
|
|
21
36
|
|
|
22
|
-
|
|
37
|
+
### Perfect Scenarios
|
|
38
|
+
- **Building microservices and distributed systems**
|
|
39
|
+
- **High-performance network services and APIs**
|
|
40
|
+
- **Cloud-native applications and DevOps tools**
|
|
41
|
+
- **Concurrent and parallel processing applications**
|
|
42
|
+
- **System programming and infrastructure tools**
|
|
43
|
+
- **CLI applications and automation scripts**
|
|
44
|
+
- **Real-time data processing and streaming**
|
|
23
45
|
|
|
24
|
-
|
|
46
|
+
### Common Triggers
|
|
47
|
+
- "Create Go microservice"
|
|
48
|
+
- "Set up Go API server"
|
|
49
|
+
- "Go concurrent programming"
|
|
50
|
+
- "Go best practices"
|
|
51
|
+
- "Optimize Go performance"
|
|
52
|
+
- "Deploy Go application"
|
|
25
53
|
|
|
26
|
-
|
|
27
|
-
- “Writing Go tests”, “How to use go tests”, “Go standard library”
|
|
28
|
-
- Automatically invoked when working with Go projects
|
|
29
|
-
- Go SPEC implementation (`/alfred:2-run`)
|
|
54
|
+
## Tool Version Matrix (2025-11-06)
|
|
30
55
|
|
|
31
|
-
|
|
56
|
+
### Core Go
|
|
57
|
+
- **Go**: 1.25.x (latest) / 1.23.x (LTS)
|
|
58
|
+
- **Gin**: 1.10.x - HTTP web framework
|
|
59
|
+
- **Echo**: 4.12.x - High-performance web framework
|
|
60
|
+
- **Chi**: 5.0.x - Lightweight router
|
|
61
|
+
- **Fiber**: 3.x - Express.js-inspired framework
|
|
32
62
|
|
|
33
|
-
|
|
34
|
-
- **
|
|
35
|
-
- **
|
|
36
|
-
- **
|
|
37
|
-
-
|
|
63
|
+
### Database & Storage
|
|
64
|
+
- **GORM**: 2.0.x - ORM for Go
|
|
65
|
+
- **sqlx**: 1.4.x - SQL extensions
|
|
66
|
+
- **pgx**: 5.6.x - PostgreSQL driver
|
|
67
|
+
- **Redis**: 9.x - Redis client
|
|
68
|
+
- **MongoDB**: 2.x - MongoDB driver
|
|
38
69
|
|
|
39
|
-
|
|
40
|
-
- **
|
|
41
|
-
- **
|
|
42
|
-
- **
|
|
43
|
-
- **
|
|
70
|
+
### Testing Tools
|
|
71
|
+
- **Testify**: 1.9.x - Testing toolkit
|
|
72
|
+
- **GoMock**: 1.6.x - Mock generation
|
|
73
|
+
- **gomock**: Built-in mocking framework
|
|
74
|
+
- **goleak**: 1.3.x - Goroutine leak detection
|
|
44
75
|
|
|
45
|
-
|
|
46
|
-
-
|
|
47
|
-
- **
|
|
48
|
-
- **
|
|
49
|
-
- **
|
|
76
|
+
### Development Tools
|
|
77
|
+
- **Air**: 1.53.x - Live reload
|
|
78
|
+
- **Gin-swagger**: 1.6.x - API documentation
|
|
79
|
+
- **Zap**: 1.27.x - Structured logging
|
|
80
|
+
- **Viper**: 1.19.x - Configuration management
|
|
81
|
+
- **golangci-lint**: 1.62.x - Linter aggregator
|
|
50
82
|
|
|
51
|
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
83
|
+
### Observability
|
|
84
|
+
- **Prometheus**: 4.x client - Metrics
|
|
85
|
+
- **Jaeger**: 2.x client - Distributed tracing
|
|
86
|
+
- **OpenTelemetry**: 1.30.x - Observability framework
|
|
87
|
+
- **pprof**: Built-in profiling
|
|
56
88
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
- Error handling: `if err != nil { return err }`
|
|
61
|
-
- Avoid naked returns in large functions
|
|
89
|
+
## Ecosystem Overview
|
|
90
|
+
|
|
91
|
+
### Project Setup (2025 Best Practice)
|
|
62
92
|
|
|
63
|
-
## Examples
|
|
64
93
|
```bash
|
|
65
|
-
|
|
94
|
+
# Modern Go project with modules
|
|
95
|
+
go mod init github.com/your-org/your-project
|
|
96
|
+
|
|
97
|
+
# Initialize project structure
|
|
98
|
+
mkdir -p {cmd/server,internal/{handler,service,repository},pkg/{utils,middleware},configs,deployments/{docker,k8s},scripts,test}
|
|
99
|
+
|
|
100
|
+
# Install essential tools
|
|
101
|
+
go install github.com/cosmtrek/air@latest
|
|
102
|
+
go install github.com/swaggo/swag/cmd/swag@latest
|
|
103
|
+
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
|
104
|
+
go install go.uber.org/mock/mockgen@latest
|
|
105
|
+
|
|
106
|
+
# Generate code documentation
|
|
107
|
+
swag init -g cmd/server/main.go
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Modern Project Structure
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
my-go-project/
|
|
114
|
+
├── go.mod
|
|
115
|
+
├── go.sum
|
|
116
|
+
├── Makefile # Build commands and utilities
|
|
117
|
+
├── README.md
|
|
118
|
+
├── .github/
|
|
119
|
+
│ └── workflows/ # CI/CD pipelines
|
|
120
|
+
├── cmd/
|
|
121
|
+
│ └── server/
|
|
122
|
+
│ └── main.go # Application entry point
|
|
123
|
+
├── internal/ # Private application code
|
|
124
|
+
│ ├── handler/ # HTTP handlers
|
|
125
|
+
│ ├── service/ # Business logic
|
|
126
|
+
│ ├── repository/ # Data access layer
|
|
127
|
+
│ ├── model/ # Domain models
|
|
128
|
+
│ └── middleware/ # HTTP middleware
|
|
129
|
+
├── pkg/ # Public library code
|
|
130
|
+
│ ├── utils/ # Utility functions
|
|
131
|
+
│ ├── config/ # Configuration
|
|
132
|
+
│ └── logger/ # Logging utilities
|
|
133
|
+
├── configs/ # Configuration files
|
|
134
|
+
├── deployments/
|
|
135
|
+
│ ├── docker/ # Docker configurations
|
|
136
|
+
│ └── k8s/ # Kubernetes manifests
|
|
137
|
+
├── scripts/ # Build and utility scripts
|
|
138
|
+
├── test/ # Integration and e2e tests
|
|
139
|
+
├── docs/ # API documentation
|
|
140
|
+
└── migrations/ # Database migrations
|
|
66
141
|
```
|
|
67
142
|
|
|
68
|
-
##
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
143
|
+
## Modern Go Patterns
|
|
144
|
+
|
|
145
|
+
### Context-First Design
|
|
146
|
+
|
|
147
|
+
```go
|
|
148
|
+
// handler/user.go
|
|
149
|
+
package handler
|
|
150
|
+
|
|
151
|
+
import (
|
|
152
|
+
"context"
|
|
153
|
+
"net/http"
|
|
154
|
+
"time"
|
|
155
|
+
|
|
156
|
+
"github.com/gin-gonic/gin"
|
|
157
|
+
"github.com/your-org/your-project/internal/model"
|
|
158
|
+
"github.com/your-org/your-project/internal/service"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
type UserHandler struct {
|
|
162
|
+
userService service.UserService
|
|
163
|
+
logger Logger
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
func NewUserHandler(userService service.UserService, logger Logger) *UserHandler {
|
|
167
|
+
return &UserHandler{
|
|
168
|
+
userService: userService,
|
|
169
|
+
logger: logger,
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// CreateUser handles user creation requests
|
|
174
|
+
func (h *UserHandler) CreateUser(c *gin.Context) {
|
|
175
|
+
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
|
|
176
|
+
defer cancel()
|
|
177
|
+
|
|
178
|
+
var req model.CreateUserRequest
|
|
179
|
+
if err := c.ShouldBindJSON(&req); err != nil {
|
|
180
|
+
h.logger.Error("Invalid request body", "error", err)
|
|
181
|
+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
user, err := h.userService.CreateUser(ctx, req)
|
|
186
|
+
if err != nil {
|
|
187
|
+
h.logger.Error("Failed to create user", "error", err)
|
|
188
|
+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error"})
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
c.JSON(http.StatusCreated, user)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// GetUser retrieves a user by ID
|
|
196
|
+
func (h *UserHandler) GetUser(c *gin.Context) {
|
|
197
|
+
ctx, cancel := context.WithTimeout(c.Request.Context(), 10*time.Second)
|
|
198
|
+
defer cancel()
|
|
199
|
+
|
|
200
|
+
userID := c.Param("id")
|
|
201
|
+
user, err := h.userService.GetUser(ctx, userID)
|
|
202
|
+
if err != nil {
|
|
203
|
+
if err == service.ErrUserNotFound {
|
|
204
|
+
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
h.logger.Error("Failed to get user", "userID", userID, "error", err)
|
|
209
|
+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error"})
|
|
210
|
+
return
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
c.JSON(http.StatusOK, user)
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Service Layer with Error Handling
|
|
218
|
+
|
|
219
|
+
```go
|
|
220
|
+
// service/user.go
|
|
221
|
+
package service
|
|
222
|
+
|
|
223
|
+
import (
|
|
224
|
+
"context"
|
|
225
|
+
"errors"
|
|
226
|
+
"fmt"
|
|
227
|
+
|
|
228
|
+
"github.com/your-org/your-project/internal/model"
|
|
229
|
+
"github.com/your-org/your-project/internal/repository"
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
var (
|
|
233
|
+
ErrUserNotFound = errors.New("user not found")
|
|
234
|
+
ErrUserExists = errors.New("user already exists")
|
|
235
|
+
ErrInvalidInput = errors.New("invalid input")
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
type UserService interface {
|
|
239
|
+
CreateUser(ctx context.Context, req model.CreateUserRequest) (*model.User, error)
|
|
240
|
+
GetUser(ctx context.Context, id string) (*model.User, error)
|
|
241
|
+
UpdateUser(ctx context.Context, id string, req model.UpdateUserRequest) (*model.User, error)
|
|
242
|
+
DeleteUser(ctx context.Context, id string) error
|
|
243
|
+
ListUsers(ctx context.Context, filter model.UserFilter) ([]*model.User, int, error)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
type userService struct {
|
|
247
|
+
userRepo repository.UserRepository
|
|
248
|
+
logger Logger
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
func NewUserService(userRepo repository.UserRepository, logger Logger) UserService {
|
|
252
|
+
return &userService{
|
|
253
|
+
userRepo: userRepo,
|
|
254
|
+
logger: logger,
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
func (s *userService) CreateUser(ctx context.Context, req model.CreateUserRequest) (*model.User, error) {
|
|
259
|
+
// Validate input
|
|
260
|
+
if err := req.Validate(); err != nil {
|
|
261
|
+
s.logger.Warn("Invalid user creation request", "error", err)
|
|
262
|
+
return nil, fmt.Errorf("%w: %v", ErrInvalidInput, err)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Check if user already exists
|
|
266
|
+
existing, err := s.userRepo.GetByEmail(ctx, req.Email)
|
|
267
|
+
if err == nil && existing != nil {
|
|
268
|
+
s.logger.Warn("User already exists", "email", req.Email)
|
|
269
|
+
return nil, ErrUserExists
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Create user
|
|
273
|
+
user := &model.User{
|
|
274
|
+
ID: generateID(),
|
|
275
|
+
Email: req.Email,
|
|
276
|
+
Name: req.Name,
|
|
277
|
+
CreatedAt: time.Now(),
|
|
278
|
+
UpdatedAt: time.Now(),
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if err := s.userRepo.Create(ctx, user); err != nil {
|
|
282
|
+
s.logger.Error("Failed to create user", "error", err)
|
|
283
|
+
return nil, fmt.Errorf("failed to create user: %w", err)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
s.logger.Info("User created successfully", "userID", user.ID)
|
|
287
|
+
return user, nil
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
func (s *userService) GetUser(ctx context.Context, id string) (*model.User, error) {
|
|
291
|
+
user, err := s.userRepo.GetByID(ctx, id)
|
|
292
|
+
if err != nil {
|
|
293
|
+
if errors.Is(err, repository.ErrNotFound) {
|
|
294
|
+
return nil, ErrUserNotFound
|
|
295
|
+
}
|
|
296
|
+
return nil, fmt.Errorf("failed to get user: %w", err)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return user, nil
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Repository Pattern with SQLx
|
|
304
|
+
|
|
305
|
+
```go
|
|
306
|
+
// repository/user.go
|
|
307
|
+
package repository
|
|
308
|
+
|
|
309
|
+
import (
|
|
310
|
+
"context"
|
|
311
|
+
"database/sql"
|
|
312
|
+
"errors"
|
|
313
|
+
"fmt"
|
|
314
|
+
|
|
315
|
+
"github.com/jmoiron/sqlx"
|
|
316
|
+
_ "github.com/lib/pq" // PostgreSQL driver
|
|
317
|
+
"github.com/your-org/your-project/internal/model"
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
var (
|
|
321
|
+
ErrNotFound = errors.New("record not found")
|
|
322
|
+
ErrDuplicate = errors.New("duplicate record")
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
type UserRepository interface {
|
|
326
|
+
Create(ctx context.Context, user *model.User) error
|
|
327
|
+
GetByID(ctx context.Context, id string) (*model.User, error)
|
|
328
|
+
GetByEmail(ctx context.Context, email string) (*model.User, error)
|
|
329
|
+
Update(ctx context.Context, user *model.User) error
|
|
330
|
+
Delete(ctx context.Context, id string) error
|
|
331
|
+
List(ctx context.Context, filter UserFilter) ([]*model.User, int, error)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
type userRepository struct {
|
|
335
|
+
db *sqlx.DB
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
func NewUserRepository(db *sqlx.DB) UserRepository {
|
|
339
|
+
return &userRepository{db: db}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
func (r *userRepository) Create(ctx context.Context, user *model.User) error {
|
|
343
|
+
query := `
|
|
344
|
+
INSERT INTO users (id, email, name, created_at, updated_at)
|
|
345
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
346
|
+
ON CONFLICT (email) DO NOTHING
|
|
347
|
+
`
|
|
348
|
+
|
|
349
|
+
result, err := r.db.ExecContext(ctx, query, user.ID, user.Email, user.Name, user.CreatedAt, user.UpdatedAt)
|
|
350
|
+
if err != nil {
|
|
351
|
+
return fmt.Errorf("failed to insert user: %w", err)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
rowsAffected, err := result.RowsAffected()
|
|
355
|
+
if err != nil {
|
|
356
|
+
return fmt.Errorf("failed to get rows affected: %w", err)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if rowsAffected == 0 {
|
|
360
|
+
return ErrDuplicate
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return nil
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
func (r *userRepository) GetByID(ctx context.Context, id string) (*model.User, error) {
|
|
367
|
+
query := `
|
|
368
|
+
SELECT id, email, name, created_at, updated_at
|
|
369
|
+
FROM users
|
|
370
|
+
WHERE id = $1
|
|
371
|
+
`
|
|
372
|
+
|
|
373
|
+
var user model.User
|
|
374
|
+
err := r.db.GetContext(ctx, &user, query, id)
|
|
375
|
+
if err != nil {
|
|
376
|
+
if errors.Is(err, sql.ErrNoRows) {
|
|
377
|
+
return nil, ErrNotFound
|
|
378
|
+
}
|
|
379
|
+
return nil, fmt.Errorf("failed to get user by ID: %w", err)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return &user, nil
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
func (r *userRepository) List(ctx context.Context, filter UserFilter) ([]*model.User, int, error) {
|
|
386
|
+
// Build dynamic query
|
|
387
|
+
baseQuery := `
|
|
388
|
+
SELECT id, email, name, created_at, updated_at
|
|
389
|
+
FROM users
|
|
390
|
+
WHERE 1=1
|
|
391
|
+
`
|
|
392
|
+
|
|
393
|
+
countQuery := `
|
|
394
|
+
SELECT COUNT(*)
|
|
395
|
+
FROM users
|
|
396
|
+
WHERE 1=1
|
|
397
|
+
`
|
|
398
|
+
|
|
399
|
+
var args []interface{}
|
|
400
|
+
conditions := []string{}
|
|
401
|
+
|
|
402
|
+
if filter.Email != "" {
|
|
403
|
+
conditions = append(conditions, "email = $"+fmt.Sprintf("%d", len(args)+1))
|
|
404
|
+
args = append(args, filter.Email)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if len(conditions) > 0 {
|
|
408
|
+
whereClause := " AND " + fmt.Sprintf(" AND ", conditions)
|
|
409
|
+
baseQuery += whereClause
|
|
410
|
+
countQuery += whereClause
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Get total count
|
|
414
|
+
var total int
|
|
415
|
+
err := r.db.GetContext(ctx, &total, countQuery, args...)
|
|
416
|
+
if err != nil {
|
|
417
|
+
return nil, 0, fmt.Errorf("failed to count users: %w", err)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Add pagination
|
|
421
|
+
if filter.Limit > 0 {
|
|
422
|
+
baseQuery += fmt.Sprintf(" LIMIT $%d OFFSET $%d", len(args)+1, len(args)+2)
|
|
423
|
+
args = append(args, filter.Limit, filter.Offset)
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
var users []*model.User
|
|
427
|
+
err = r.db.SelectContext(ctx, &users, baseQuery, args...)
|
|
428
|
+
if err != nil {
|
|
429
|
+
return nil, 0, fmt.Errorf("failed to list users: %w", err)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return users, total, nil
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
## Concurrent Programming
|
|
437
|
+
|
|
438
|
+
### Worker Pool Pattern
|
|
439
|
+
|
|
440
|
+
```go
|
|
441
|
+
// pkg/worker/pool.go
|
|
442
|
+
package worker
|
|
443
|
+
|
|
444
|
+
import (
|
|
445
|
+
"context"
|
|
446
|
+
"sync"
|
|
447
|
+
"time"
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
type Task[T any] interface {
|
|
451
|
+
Execute(ctx context.Context) (T, error)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
type Result[T any] struct {
|
|
455
|
+
Value T
|
|
456
|
+
Error error
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
type WorkerPool[T any] struct {
|
|
460
|
+
workers int
|
|
461
|
+
taskQueue chan Task[T]
|
|
462
|
+
wg sync.WaitGroup
|
|
463
|
+
ctx context.Context
|
|
464
|
+
cancel context.CancelFunc
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
func NewWorkerPool[T any](workers int) *WorkerPool[T] {
|
|
468
|
+
ctx, cancel := context.WithCancel(context.Background())
|
|
469
|
+
|
|
470
|
+
return &WorkerPool[T]{
|
|
471
|
+
workers: workers,
|
|
472
|
+
taskQueue: make(chan Task[T], workers*2),
|
|
473
|
+
ctx: ctx,
|
|
474
|
+
cancel: cancel,
|
|
475
|
+
}
|
|
476
|
+
}
|
|
72
477
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
478
|
+
func (p *WorkerPool[T]) Start() {
|
|
479
|
+
for i := 0; i < p.workers; i++ {
|
|
480
|
+
p.wg.Add(1)
|
|
481
|
+
go p.worker(i)
|
|
482
|
+
}
|
|
483
|
+
}
|
|
76
484
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
485
|
+
func (p *WorkerPool[T]) worker(id int) {
|
|
486
|
+
defer p.wg.Done()
|
|
487
|
+
|
|
488
|
+
for {
|
|
489
|
+
select {
|
|
490
|
+
case task := <-p.taskQueue:
|
|
491
|
+
if task == nil {
|
|
492
|
+
return
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
result, err := task.Execute(p.ctx)
|
|
496
|
+
if err != nil {
|
|
497
|
+
// Handle error (could use error channel or logging)
|
|
498
|
+
continue
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Process result
|
|
502
|
+
|
|
503
|
+
case <-p.ctx.Done():
|
|
504
|
+
return
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
80
508
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
509
|
+
func (p *WorkerPool[T]) Submit(task Task[T]) {
|
|
510
|
+
select {
|
|
511
|
+
case p.taskQueue <- task:
|
|
512
|
+
case <-p.ctx.Done():
|
|
513
|
+
return
|
|
514
|
+
}
|
|
515
|
+
}
|
|
84
516
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
517
|
+
func (p *WorkerPool[T]) Stop() {
|
|
518
|
+
close(p.taskQueue)
|
|
519
|
+
p.cancel()
|
|
520
|
+
p.wg.Wait()
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Example usage
|
|
524
|
+
type ImageProcessingTask struct {
|
|
525
|
+
ImagePath string
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
func (t *ImageProcessingTask) Execute(ctx context.Context) (string, error) {
|
|
529
|
+
// Simulate image processing
|
|
530
|
+
time.Sleep(100 * time.Millisecond)
|
|
531
|
+
return "processed_" + t.ImagePath, nil
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
func ProcessImages(imagePaths []string) []string {
|
|
535
|
+
pool := NewWorkerPool[string](10)
|
|
536
|
+
pool.Start()
|
|
537
|
+
defer pool.Stop()
|
|
538
|
+
|
|
539
|
+
var results []string
|
|
540
|
+
var mu sync.Mutex
|
|
541
|
+
|
|
542
|
+
for _, path := range imagePaths {
|
|
543
|
+
task := &ImageProcessingTask{ImagePath: path}
|
|
544
|
+
|
|
545
|
+
go func(t *ImageProcessingTask) {
|
|
546
|
+
result, err := t.Execute(context.Background())
|
|
547
|
+
if err == nil {
|
|
548
|
+
mu.Lock()
|
|
549
|
+
results = append(results, result)
|
|
550
|
+
mu.Unlock()
|
|
551
|
+
}
|
|
552
|
+
}(task)
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return results
|
|
556
|
+
}
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### Fan-In/Fan-Out Pattern
|
|
560
|
+
|
|
561
|
+
```go
|
|
562
|
+
// pkg/concurrent/fan.go
|
|
563
|
+
package concurrent
|
|
564
|
+
|
|
565
|
+
import (
|
|
566
|
+
"context"
|
|
567
|
+
"sync"
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
// FanIn merges multiple input channels into a single output channel
|
|
571
|
+
func FanIn[T any](ctx context.Context, channels ...<-chan T) <-chan T {
|
|
572
|
+
var wg sync.WaitGroup
|
|
573
|
+
out := make(chan T)
|
|
574
|
+
|
|
575
|
+
output := func(c <-chan T) {
|
|
576
|
+
defer wg.Done()
|
|
577
|
+
for {
|
|
578
|
+
select {
|
|
579
|
+
case v, ok := <-c:
|
|
580
|
+
if !ok {
|
|
581
|
+
return
|
|
582
|
+
}
|
|
583
|
+
select {
|
|
584
|
+
case out <- v:
|
|
585
|
+
case <-ctx.Done():
|
|
586
|
+
return
|
|
587
|
+
}
|
|
588
|
+
case <-ctx.Done():
|
|
589
|
+
return
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
wg.Add(len(channels))
|
|
595
|
+
for _, c := range channels {
|
|
596
|
+
go output(c)
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
go func() {
|
|
600
|
+
wg.Wait()
|
|
601
|
+
close(out)
|
|
602
|
+
}()
|
|
603
|
+
|
|
604
|
+
return out
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// FanOut distributes input across multiple output channels
|
|
608
|
+
func FanOut[T any](ctx context.Context, in <-chan T, n int) []<-chan T {
|
|
609
|
+
outs := make([]chan T, n)
|
|
610
|
+
for i := 0; i < n; i++ {
|
|
611
|
+
outs[i] = make(chan T)
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
distribute := func() {
|
|
615
|
+
defer func() {
|
|
616
|
+
for _, out := range outs {
|
|
617
|
+
close(out)
|
|
618
|
+
}
|
|
619
|
+
}()
|
|
620
|
+
|
|
621
|
+
for {
|
|
622
|
+
select {
|
|
623
|
+
case v, ok := <-in:
|
|
624
|
+
if !ok {
|
|
625
|
+
return
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
for _, out := range outs {
|
|
629
|
+
select {
|
|
630
|
+
case out <- v:
|
|
631
|
+
case <-ctx.Done():
|
|
632
|
+
return
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
case <-ctx.Done():
|
|
637
|
+
return
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
go distribute()
|
|
643
|
+
return outs
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Pipeline combines fan-out and fan-in for parallel processing
|
|
647
|
+
func Pipeline[T, R any](
|
|
648
|
+
ctx context.Context,
|
|
649
|
+
input []T,
|
|
650
|
+
workers int,
|
|
651
|
+
process func(context.Context, T) (R, error),
|
|
652
|
+
) (<-chan R, <-chan error) {
|
|
653
|
+
out := make(chan R, workers)
|
|
654
|
+
errChan := make(chan error, workers)
|
|
655
|
+
|
|
656
|
+
// Create input channel
|
|
657
|
+
in := make(chan T, len(input))
|
|
658
|
+
for _, item := range input {
|
|
659
|
+
in <- item
|
|
660
|
+
}
|
|
661
|
+
close(in)
|
|
662
|
+
|
|
663
|
+
// Fan out to workers
|
|
664
|
+
channels := FanOut(ctx, in, workers)
|
|
665
|
+
|
|
666
|
+
// Process each item
|
|
667
|
+
var wg sync.WaitGroup
|
|
668
|
+
for _, ch := range channels {
|
|
669
|
+
wg.Add(1)
|
|
670
|
+
go func(c <-chan T) {
|
|
671
|
+
defer wg.Done()
|
|
672
|
+
for item := range c {
|
|
673
|
+
result, err := process(ctx, item)
|
|
674
|
+
if err != nil {
|
|
675
|
+
select {
|
|
676
|
+
case errChan <- err:
|
|
677
|
+
case <-ctx.Done():
|
|
678
|
+
return
|
|
679
|
+
}
|
|
680
|
+
continue
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
select {
|
|
684
|
+
case out <- result:
|
|
685
|
+
case <-ctx.Done():
|
|
686
|
+
return
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}(ch)
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
go func() {
|
|
693
|
+
wg.Wait()
|
|
694
|
+
close(out)
|
|
695
|
+
close(errChan)
|
|
696
|
+
}()
|
|
697
|
+
|
|
698
|
+
return out, errChan
|
|
699
|
+
}
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
## Performance Optimization
|
|
703
|
+
|
|
704
|
+
### Memory Pooling
|
|
705
|
+
|
|
706
|
+
```go
|
|
707
|
+
// pkg/pool/buffer.go
|
|
708
|
+
package pool
|
|
709
|
+
|
|
710
|
+
import (
|
|
711
|
+
"sync"
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
var (
|
|
715
|
+
bufferPool = sync.Pool{
|
|
716
|
+
New: func() interface{} {
|
|
717
|
+
return make([]byte, 0, 1024) // Pre-allocate 1KB
|
|
718
|
+
},
|
|
719
|
+
}
|
|
720
|
+
)
|
|
721
|
+
|
|
722
|
+
// GetBuffer returns a buffer from the pool
|
|
723
|
+
func GetBuffer() []byte {
|
|
724
|
+
return bufferPool.Get().([]byte)
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// PutBuffer returns a buffer to the pool
|
|
728
|
+
func PutBuffer(buf []byte) {
|
|
729
|
+
if cap(buf) <= 64*1024 { // Don't pool large buffers
|
|
730
|
+
bufferPool.Put(buf[:0]) // Reset length but keep capacity
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// Usage example
|
|
735
|
+
func ProcessData(data []byte) ([]byte, error) {
|
|
736
|
+
buf := GetBuffer()
|
|
737
|
+
defer PutBuffer(buf)
|
|
738
|
+
|
|
739
|
+
// Process data using the pooled buffer
|
|
740
|
+
buf = append(buf, data...)
|
|
741
|
+
// ... processing logic ...
|
|
742
|
+
|
|
743
|
+
result := make([]byte, len(buf))
|
|
744
|
+
copy(result, buf)
|
|
745
|
+
|
|
746
|
+
return result, nil
|
|
747
|
+
}
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
### String Building Optimization
|
|
751
|
+
|
|
752
|
+
```go
|
|
753
|
+
// pkg/utils/strings.go
|
|
754
|
+
package utils
|
|
755
|
+
|
|
756
|
+
import (
|
|
757
|
+
"strings"
|
|
758
|
+
"sync"
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
var stringBuilderPool = sync.Pool{
|
|
762
|
+
New: func() interface{} {
|
|
763
|
+
return &strings.Builder{}
|
|
764
|
+
},
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// StringBuilderPool provides a thread-safe string builder pool
|
|
768
|
+
func GetStringBuilder() *strings.Builder {
|
|
769
|
+
sb := stringBuilderPool.Get().(*strings.Builder)
|
|
770
|
+
sb.Reset()
|
|
771
|
+
return sb
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
func PutStringBuilder(sb *strings.Builder) {
|
|
775
|
+
if sb.Cap() <= 4096 { // Don't pool large builders
|
|
776
|
+
stringBuilderPool.Put(sb)
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// FastJoin efficiently joins strings using a pooled builder
|
|
781
|
+
func FastJoin(strs ...string) string {
|
|
782
|
+
if len(strs) == 0 {
|
|
783
|
+
return ""
|
|
784
|
+
}
|
|
785
|
+
if len(strs) == 1 {
|
|
786
|
+
return strs[0]
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
sb := GetStringBuilder()
|
|
790
|
+
defer PutStringBuilder(sb)
|
|
791
|
+
|
|
792
|
+
for _, s := range strs {
|
|
793
|
+
sb.WriteString(s)
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
return sb.String()
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// Usage example
|
|
800
|
+
func BuildMessage(parts []string) string {
|
|
801
|
+
sb := GetStringBuilder()
|
|
802
|
+
defer PutStringBuilder(sb)
|
|
803
|
+
|
|
804
|
+
for i, part := range parts {
|
|
805
|
+
if i > 0 {
|
|
806
|
+
sb.WriteString(" ")
|
|
807
|
+
}
|
|
808
|
+
sb.WriteString(part)
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
return sb.String()
|
|
812
|
+
}
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
## Testing Strategies
|
|
816
|
+
|
|
817
|
+
### Comprehensive Unit Testing
|
|
818
|
+
|
|
819
|
+
```go
|
|
820
|
+
// service/user_test.go
|
|
821
|
+
package service
|
|
822
|
+
|
|
823
|
+
import (
|
|
824
|
+
"context"
|
|
825
|
+
"errors"
|
|
826
|
+
"testing"
|
|
827
|
+
"time"
|
|
828
|
+
|
|
829
|
+
"github.com/stretchr/testify/assert"
|
|
830
|
+
"github.com/stretchr/testify/mock"
|
|
831
|
+
"github.com/stretchr/testify/require"
|
|
832
|
+
"github.com/your-org/your-project/internal/model"
|
|
833
|
+
"github.com/your-org/your-project/internal/repository"
|
|
834
|
+
)
|
|
835
|
+
|
|
836
|
+
// MockUserRepository is a mock implementation of UserRepository
|
|
837
|
+
type MockUserRepository struct {
|
|
838
|
+
mock.Mock
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
func (m *MockUserRepository) Create(ctx context.Context, user *model.User) error {
|
|
842
|
+
args := m.Called(ctx, user)
|
|
843
|
+
return args.Error(0)
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
func (m *MockUserRepository) GetByID(ctx context.Context, id string) (*model.User, error) {
|
|
847
|
+
args := m.Called(ctx, id)
|
|
848
|
+
if args.Get(0) == nil {
|
|
849
|
+
return nil, args.Error(1)
|
|
850
|
+
}
|
|
851
|
+
return args.Get(0).(*model.User), args.Error(1)
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
func (m *MockUserRepository) GetByEmail(ctx context.Context, email string) (*model.User, error) {
|
|
855
|
+
args := m.Called(ctx, email)
|
|
856
|
+
if args.Get(0) == nil {
|
|
857
|
+
return nil, args.Error(1)
|
|
858
|
+
}
|
|
859
|
+
return args.Get(0).(*model.User), args.Error(1)
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
func TestUserService_CreateUser_Success(t *testing.T) {
|
|
863
|
+
// Arrange
|
|
864
|
+
mockRepo := new(MockUserRepository)
|
|
865
|
+
mockLogger := &MockLogger{} // Assume you have this
|
|
866
|
+
service := NewUserService(mockRepo, mockLogger)
|
|
867
|
+
|
|
868
|
+
req := model.CreateUserRequest{
|
|
869
|
+
Email: "test@example.com",
|
|
870
|
+
Name: "Test User",
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
expectedUser := &model.User{
|
|
874
|
+
ID: "user-123",
|
|
875
|
+
Email: req.Email,
|
|
876
|
+
Name: req.Name,
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
mockRepo.On("GetByEmail", mock.Anything, req.Email).Return(nil, repository.ErrNotFound)
|
|
880
|
+
mockRepo.On("Create", mock.Anything, mock.AnythingOfType("*model.User")).Return(nil)
|
|
881
|
+
|
|
882
|
+
// Act
|
|
883
|
+
ctx := context.Background()
|
|
884
|
+
user, err := service.CreateUser(ctx, req)
|
|
885
|
+
|
|
886
|
+
// Assert
|
|
887
|
+
require.NoError(t, err)
|
|
888
|
+
assert.NotNil(t, user)
|
|
889
|
+
assert.Equal(t, req.Email, user.Email)
|
|
890
|
+
assert.Equal(t, req.Name, user.Name)
|
|
891
|
+
mockRepo.AssertExpectations(t)
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
func TestUserService_CreateUser_EmailExists(t *testing.T) {
|
|
895
|
+
// Arrange
|
|
896
|
+
mockRepo := new(MockUserRepository)
|
|
897
|
+
mockLogger := &MockLogger{}
|
|
898
|
+
service := NewUserService(mockRepo, mockLogger)
|
|
899
|
+
|
|
900
|
+
req := model.CreateUserRequest{
|
|
901
|
+
Email: "existing@example.com",
|
|
902
|
+
Name: "Test User",
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
existingUser := &model.User{
|
|
906
|
+
ID: "existing-123",
|
|
907
|
+
Email: req.Email,
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
mockRepo.On("GetByEmail", mock.Anything, req.Email).Return(existingUser, nil)
|
|
911
|
+
|
|
912
|
+
// Act
|
|
913
|
+
ctx := context.Background()
|
|
914
|
+
user, err := service.CreateUser(ctx, req)
|
|
915
|
+
|
|
916
|
+
// Assert
|
|
917
|
+
require.Error(t, err)
|
|
918
|
+
assert.Nil(t, user)
|
|
919
|
+
assert.Equal(t, ErrUserExists, err)
|
|
920
|
+
mockRepo.AssertExpectations(t)
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
func TestUserService_GetUser_NotFound(t *testing.T) {
|
|
924
|
+
// Arrange
|
|
925
|
+
mockRepo := new(MockUserRepository)
|
|
926
|
+
mockLogger := &MockLogger{}
|
|
927
|
+
service := NewUserService(mockRepo, mockLogger)
|
|
928
|
+
|
|
929
|
+
userID := "nonexistent"
|
|
930
|
+
mockRepo.On("GetByID", mock.Anything, userID).Return(nil, repository.ErrNotFound)
|
|
931
|
+
|
|
932
|
+
// Act
|
|
933
|
+
ctx := context.Background()
|
|
934
|
+
user, err := service.GetUser(ctx, userID)
|
|
935
|
+
|
|
936
|
+
// Assert
|
|
937
|
+
require.Error(t, err)
|
|
938
|
+
assert.Nil(t, user)
|
|
939
|
+
assert.Equal(t, ErrUserNotFound, err)
|
|
940
|
+
mockRepo.AssertExpectations(t)
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// Benchmark example
|
|
944
|
+
func BenchmarkUserService_CreateUser(b *testing.B) {
|
|
945
|
+
mockRepo := new(MockUserRepository)
|
|
946
|
+
mockLogger := &MockLogger{}
|
|
947
|
+
service := NewUserService(mockRepo, mockLogger)
|
|
948
|
+
|
|
949
|
+
req := model.CreateUserRequest{
|
|
950
|
+
Email: "test@example.com",
|
|
951
|
+
Name: "Test User",
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
mockRepo.On("GetByEmail", mock.Anything, req.Email).Return(nil, repository.ErrNotFound)
|
|
955
|
+
mockRepo.On("Create", mock.Anything, mock.AnythingOfType("*model.User")).Return(nil)
|
|
956
|
+
|
|
957
|
+
ctx := context.Background()
|
|
958
|
+
|
|
959
|
+
b.ResetTimer()
|
|
960
|
+
for i := 0; i < b.N; i++ {
|
|
961
|
+
_, err := service.CreateUser(ctx, req)
|
|
962
|
+
if err != nil {
|
|
963
|
+
b.Fatal(err)
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
```
|
|
968
|
+
|
|
969
|
+
### Integration Testing with Testcontainers
|
|
970
|
+
|
|
971
|
+
```go
|
|
972
|
+
// test/integration/user_test.go
|
|
973
|
+
package integration
|
|
974
|
+
|
|
975
|
+
import (
|
|
976
|
+
"context"
|
|
977
|
+
"fmt"
|
|
978
|
+
"testing"
|
|
979
|
+
"time"
|
|
980
|
+
|
|
981
|
+
"github.com/stretchr/testify/assert"
|
|
982
|
+
"github.com/stretchr/testify/require"
|
|
983
|
+
"github.com/testcontainers/testcontainers-go"
|
|
984
|
+
"github.com/testcontainers/testcontainers-go/wait"
|
|
985
|
+
|
|
986
|
+
"github.com/your-org/your-project/internal/model"
|
|
987
|
+
"github.com/your-org/your-project/internal/repository"
|
|
988
|
+
"github.com/your-org/your-project/internal/service"
|
|
989
|
+
"github.com/your-org/your-project/pkg/config"
|
|
990
|
+
)
|
|
991
|
+
|
|
992
|
+
func TestUserService_Integration(t *testing.T) {
|
|
993
|
+
// Setup test database container
|
|
994
|
+
ctx := context.Background()
|
|
995
|
+
|
|
996
|
+
req := testcontainers.ContainerRequest{
|
|
997
|
+
Image: "postgres:16",
|
|
998
|
+
ExposedPorts: []string{"5432/tcp"},
|
|
999
|
+
Env: map[string]string{
|
|
1000
|
+
"POSTGRES_DB": "testdb",
|
|
1001
|
+
"POSTGRES_USER": "test",
|
|
1002
|
+
"POSTGRES_PASSWORD": "test",
|
|
1003
|
+
},
|
|
1004
|
+
WaitingFor: wait.ForLog("database system is ready to accept connections"),
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
|
|
1008
|
+
ContainerRequest: req,
|
|
1009
|
+
Started: true,
|
|
1010
|
+
})
|
|
1011
|
+
require.NoError(t, err)
|
|
1012
|
+
defer container.Terminate(ctx)
|
|
1013
|
+
|
|
1014
|
+
// Get database connection info
|
|
1015
|
+
host, err := container.Host(ctx)
|
|
1016
|
+
require.NoError(t, err)
|
|
1017
|
+
|
|
1018
|
+
port, err := container.MappedPort(ctx, "5432")
|
|
1019
|
+
require.NoError(t, err)
|
|
1020
|
+
|
|
1021
|
+
// Connect to database
|
|
1022
|
+
dbConfig := config.Database{
|
|
1023
|
+
Host: host,
|
|
1024
|
+
Port: port.Int(),
|
|
1025
|
+
User: "test",
|
|
1026
|
+
Password: "test",
|
|
1027
|
+
DBName: "testdb",
|
|
1028
|
+
SSLMode: "disable",
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
db, err := setupTestDB(dbConfig)
|
|
1032
|
+
require.NoError(t, err)
|
|
1033
|
+
defer db.Close()
|
|
1034
|
+
|
|
1035
|
+
// Run migrations
|
|
1036
|
+
err = runMigrations(db)
|
|
1037
|
+
require.NoError(t, err)
|
|
1038
|
+
|
|
1039
|
+
// Setup service
|
|
1040
|
+
userRepo := repository.NewUserRepository(db)
|
|
1041
|
+
userService := service.NewUserService(userRepo, &MockLogger{})
|
|
1042
|
+
|
|
1043
|
+
// Test user creation
|
|
1044
|
+
t.Run("Create and Get User", func(t *testing.T) {
|
|
1045
|
+
req := model.CreateUserRequest{
|
|
1046
|
+
Email: "integration@example.com",
|
|
1047
|
+
Name: "Integration Test User",
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// Create user
|
|
1051
|
+
user, err := userService.CreateUser(ctx, req)
|
|
1052
|
+
require.NoError(t, err)
|
|
1053
|
+
assert.NotEmpty(t, user.ID)
|
|
1054
|
+
assert.Equal(t, req.Email, user.Email)
|
|
1055
|
+
assert.Equal(t, req.Name, user.Name)
|
|
1056
|
+
assert.False(t, user.CreatedAt.IsZero())
|
|
1057
|
+
|
|
1058
|
+
// Get user
|
|
1059
|
+
retrievedUser, err := userService.GetUser(ctx, user.ID)
|
|
1060
|
+
require.NoError(t, err)
|
|
1061
|
+
assert.Equal(t, user.ID, retrievedUser.ID)
|
|
1062
|
+
assert.Equal(t, user.Email, retrievedUser.Email)
|
|
1063
|
+
assert.Equal(t, user.Name, retrievedUser.Name)
|
|
1064
|
+
})
|
|
1065
|
+
|
|
1066
|
+
t.Run("List Users", func(t *testing.T) {
|
|
1067
|
+
// Create multiple users
|
|
1068
|
+
for i := 0; i < 5; i++ {
|
|
1069
|
+
req := model.CreateUserRequest{
|
|
1070
|
+
Email: fmt.Sprintf("user%d@example.com", i),
|
|
1071
|
+
Name: fmt.Sprintf("User %d", i),
|
|
1072
|
+
}
|
|
1073
|
+
_, err := userService.CreateUser(ctx, req)
|
|
1074
|
+
require.NoError(t, err)
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// List users
|
|
1078
|
+
users, total, err := userService.ListUsers(ctx, model.UserFilter{
|
|
1079
|
+
Limit: 10,
|
|
1080
|
+
Offset: 0,
|
|
1081
|
+
})
|
|
1082
|
+
|
|
1083
|
+
require.NoError(t, err)
|
|
1084
|
+
assert.Equal(t, 6, total) // 5 new users + 1 from previous test
|
|
1085
|
+
assert.Equal(t, 6, len(users))
|
|
1086
|
+
})
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// Helper functions
|
|
1090
|
+
func setupTestDB(cfg config.Database) (*sqlx.DB, error) {
|
|
1091
|
+
dsn := fmt.Sprintf(
|
|
1092
|
+
"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
|
|
1093
|
+
cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.DBName, cfg.SSLMode,
|
|
1094
|
+
)
|
|
1095
|
+
|
|
1096
|
+
db, err := sqlx.Connect(context.Background(), "postgres", dsn)
|
|
1097
|
+
if err != nil {
|
|
1098
|
+
return nil, err
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// Wait for database to be ready
|
|
1102
|
+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
1103
|
+
defer cancel()
|
|
1104
|
+
|
|
1105
|
+
for {
|
|
1106
|
+
err := db.PingContext(ctx)
|
|
1107
|
+
if err == nil {
|
|
1108
|
+
break
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
select {
|
|
1112
|
+
case <-time.After(1 * time.Second):
|
|
1113
|
+
continue
|
|
1114
|
+
case <-ctx.Done():
|
|
1115
|
+
return nil, ctx.Err()
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
return db, nil
|
|
1120
|
+
}
|
|
1121
|
+
```
|
|
88
1122
|
|
|
89
|
-
##
|
|
90
|
-
- 2025-03-29: Input/output/failure response/reference information for each language has been specified.
|
|
1123
|
+
## Security Best Practices
|
|
91
1124
|
|
|
92
|
-
|
|
1125
|
+
### Input Validation and Sanitization
|
|
1126
|
+
|
|
1127
|
+
```go
|
|
1128
|
+
// pkg/validation/validator.go
|
|
1129
|
+
package validation
|
|
1130
|
+
|
|
1131
|
+
import (
|
|
1132
|
+
"regexp"
|
|
1133
|
+
"strings"
|
|
1134
|
+
"unicode"
|
|
1135
|
+
|
|
1136
|
+
"github.com/go-playground/validator/v10"
|
|
1137
|
+
)
|
|
1138
|
+
|
|
1139
|
+
var (
|
|
1140
|
+
validate = validator.New()
|
|
1141
|
+
|
|
1142
|
+
// Email regex pattern
|
|
1143
|
+
emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
|
|
1144
|
+
|
|
1145
|
+
// Strong password regex
|
|
1146
|
+
passwordRegex = regexp.MustCompile(`^.{8,}$`) // At least 8 characters
|
|
1147
|
+
)
|
|
1148
|
+
|
|
1149
|
+
// ValidationResult represents validation result
|
|
1150
|
+
type ValidationResult struct {
|
|
1151
|
+
Valid bool
|
|
1152
|
+
Errors map[string]string
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
// New creates a new validator
|
|
1156
|
+
func New() *Validator {
|
|
1157
|
+
return &Validator{}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// Validator handles input validation
|
|
1161
|
+
type Validator struct{}
|
|
1162
|
+
|
|
1163
|
+
// ValidateStruct validates a struct using validator tags
|
|
1164
|
+
func (v *Validator) ValidateStruct(s interface{}) ValidationResult {
|
|
1165
|
+
err := validate.Struct(s)
|
|
1166
|
+
if err == nil {
|
|
1167
|
+
return ValidationResult{Valid: true}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
errors := make(map[string]string)
|
|
1171
|
+
for _, err := range err.(validator.ValidationErrors) {
|
|
1172
|
+
field := err.Field()
|
|
1173
|
+
switch err.Tag() {
|
|
1174
|
+
case "required":
|
|
1175
|
+
errors[field] = field + " is required"
|
|
1176
|
+
case "email":
|
|
1177
|
+
errors[field] = field + " must be a valid email address"
|
|
1178
|
+
case "min":
|
|
1179
|
+
errors[field] = field + " must be at least " + err.Param() + " characters"
|
|
1180
|
+
case "max":
|
|
1181
|
+
errors[field] = field + " must be at most " + err.Param() + " characters"
|
|
1182
|
+
default:
|
|
1183
|
+
errors[field] = field + " is invalid"
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
return ValidationResult{Valid: false, Errors: errors}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// ValidateEmail validates email format
|
|
1191
|
+
func (v *Validator) ValidateEmail(email string) bool {
|
|
1192
|
+
email = strings.TrimSpace(strings.ToLower(email))
|
|
1193
|
+
return emailRegex.MatchString(email) && len(email) <= 254
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
// ValidatePassword validates password strength
|
|
1197
|
+
func (v *Validator) ValidatePassword(password string) ValidationResult {
|
|
1198
|
+
errors := make(map[string]string)
|
|
1199
|
+
|
|
1200
|
+
if len(password) < 8 {
|
|
1201
|
+
errors["length"] = "Password must be at least 8 characters long"
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
if !containsUppercase(password) {
|
|
1205
|
+
errors["uppercase"] = "Password must contain at least one uppercase letter"
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
if !containsLowercase(password) {
|
|
1209
|
+
errors["lowercase"] = "Password must contain at least one lowercase letter"
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
if !containsDigit(password) {
|
|
1213
|
+
errors["digit"] = "Password must contain at least one digit"
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
if !containsSpecialChar(password) {
|
|
1217
|
+
errors["special"] = "Password must contain at least one special character"
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
return ValidationResult{
|
|
1221
|
+
Valid: len(errors) == 0,
|
|
1222
|
+
Errors: errors,
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
// SanitizeInput sanitizes user input
|
|
1227
|
+
func (v *Validator) SanitizeInput(input string) string {
|
|
1228
|
+
// Remove potentially dangerous characters
|
|
1229
|
+
sanitized := strings.ReplaceAll(input, "<", "<")
|
|
1230
|
+
sanitized = strings.ReplaceAll(sanitized, ">", ">")
|
|
1231
|
+
sanitized = strings.ReplaceAll(sanitized, "&", "&")
|
|
1232
|
+
sanitized = strings.ReplaceAll(sanitized, "\"", """)
|
|
1233
|
+
sanitized = strings.ReplaceAll(sanitized, "'", "'")
|
|
1234
|
+
|
|
1235
|
+
// Trim whitespace
|
|
1236
|
+
return strings.TrimSpace(sanitized)
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// Helper functions
|
|
1240
|
+
func containsUppercase(s string) bool {
|
|
1241
|
+
for _, r := range s {
|
|
1242
|
+
if unicode.IsUpper(r) {
|
|
1243
|
+
return true
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
return false
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
func containsLowercase(s string) bool {
|
|
1250
|
+
for _, r := range s {
|
|
1251
|
+
if unicode.IsLower(r) {
|
|
1252
|
+
return true
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
return false
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
func containsDigit(s string) bool {
|
|
1259
|
+
for _, r := range s {
|
|
1260
|
+
if unicode.IsDigit(r) {
|
|
1261
|
+
return true
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
return false
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
func containsSpecialChar(s string) bool {
|
|
1268
|
+
for _, r := range s {
|
|
1269
|
+
if !unicode.IsLetter(r) && !unicode.IsDigit(r) {
|
|
1270
|
+
return true
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
return false
|
|
1274
|
+
}
|
|
1275
|
+
```
|
|
1276
|
+
|
|
1277
|
+
### Authentication and Authorization
|
|
1278
|
+
|
|
1279
|
+
```go
|
|
1280
|
+
// pkg/auth/jwt.go
|
|
1281
|
+
package auth
|
|
1282
|
+
|
|
1283
|
+
import (
|
|
1284
|
+
"fmt"
|
|
1285
|
+
"time"
|
|
1286
|
+
|
|
1287
|
+
"github.com/golang-jwt/jwt/v5"
|
|
1288
|
+
)
|
|
1289
|
+
|
|
1290
|
+
// Claims represents JWT claims
|
|
1291
|
+
type Claims struct {
|
|
1292
|
+
UserID string `json:"user_id"`
|
|
1293
|
+
Email string `json:"email"`
|
|
1294
|
+
Role string `json:"role"`
|
|
1295
|
+
jwt.RegisteredClaims
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// JWTManager handles JWT token operations
|
|
1299
|
+
type JWTManager struct {
|
|
1300
|
+
secretKey []byte
|
|
1301
|
+
expiration time.Duration
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
// NewJWTManager creates a new JWT manager
|
|
1305
|
+
func NewJWTManager(secretKey string, expiration time.Duration) *JWTManager {
|
|
1306
|
+
return &JWTManager{
|
|
1307
|
+
secretKey: []byte(secretKey),
|
|
1308
|
+
expiration: expiration,
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
// GenerateToken generates a new JWT token
|
|
1313
|
+
func (m *JWTManager) GenerateToken(userID, email, role string) (string, error) {
|
|
1314
|
+
claims := &Claims{
|
|
1315
|
+
UserID: userID,
|
|
1316
|
+
Email: email,
|
|
1317
|
+
Role: role,
|
|
1318
|
+
RegisteredClaims: jwt.RegisteredClaims{
|
|
1319
|
+
ExpiresAt: jwt.NewNumericDate(time.Now().Add(m.expiration)),
|
|
1320
|
+
IssuedAt: jwt.NewNumericDate(time.Now()),
|
|
1321
|
+
NotBefore: jwt.NewNumericDate(time.Now()),
|
|
1322
|
+
Issuer: "your-app",
|
|
1323
|
+
Subject: userID,
|
|
1324
|
+
},
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
1328
|
+
return token.SignedString(m.secretKey)
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
// ValidateToken validates and parses a JWT token
|
|
1332
|
+
func (m *JWTManager) ValidateToken(tokenString string) (*Claims, error) {
|
|
1333
|
+
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
|
1334
|
+
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
|
1335
|
+
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
|
1336
|
+
}
|
|
1337
|
+
return m.secretKey, nil
|
|
1338
|
+
})
|
|
1339
|
+
|
|
1340
|
+
if err != nil {
|
|
1341
|
+
return nil, err
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
claims, ok := token.Claims.(*Claims)
|
|
1345
|
+
if !ok || !token.Valid {
|
|
1346
|
+
return nil, fmt.Errorf("invalid token")
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
return claims, nil
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// RefreshToken generates a new token with extended expiration
|
|
1353
|
+
func (m *JWTManager) RefreshToken(claims *Claims) (string, error) {
|
|
1354
|
+
claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(m.expiration))
|
|
1355
|
+
claims.IssuedAt = jwt.NewNumericDate(time.Now())
|
|
1356
|
+
|
|
1357
|
+
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
1358
|
+
return token.SignedString(m.secretKey)
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
// Middleware for authentication
|
|
1362
|
+
func (m *JWTManager) AuthMiddleware(next http.Handler) http.Handler {
|
|
1363
|
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
1364
|
+
authHeader := r.Header.Get("Authorization")
|
|
1365
|
+
if authHeader == "" {
|
|
1366
|
+
http.Error(w, "Authorization header required", http.StatusUnauthorized)
|
|
1367
|
+
return
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
tokenString := strings.Replace(authHeader, "Bearer ", "", 1)
|
|
1371
|
+
claims, err := m.ValidateToken(tokenString)
|
|
1372
|
+
if err != nil {
|
|
1373
|
+
http.Error(w, "Invalid token", http.StatusUnauthorized)
|
|
1374
|
+
return
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
// Add claims to context
|
|
1378
|
+
ctx := context.WithValue(r.Context(), "user_claims", claims)
|
|
1379
|
+
next.ServeHTTP(w, r.WithContext(ctx))
|
|
1380
|
+
})
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
// Role-based authorization middleware
|
|
1384
|
+
func (m *JWTManager) RequireRole(requiredRole string) func(http.Handler) http.Handler {
|
|
1385
|
+
return func(next http.Handler) http.Handler {
|
|
1386
|
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
1387
|
+
claims, ok := r.Context().Value("user_claims").(*Claims)
|
|
1388
|
+
if !ok {
|
|
1389
|
+
http.Error(w, "User claims not found", http.StatusUnauthorized)
|
|
1390
|
+
return
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
if claims.Role != requiredRole {
|
|
1394
|
+
http.Error(w, "Insufficient permissions", http.StatusForbidden)
|
|
1395
|
+
return
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
next.ServeHTTP(w, r)
|
|
1399
|
+
})
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
```
|
|
1403
|
+
|
|
1404
|
+
## Integration Patterns
|
|
1405
|
+
|
|
1406
|
+
### gRPC Service Implementation
|
|
1407
|
+
|
|
1408
|
+
```go
|
|
1409
|
+
// api/proto/user.proto
|
|
1410
|
+
syntax = "proto3";
|
|
1411
|
+
|
|
1412
|
+
package user.v1;
|
|
1413
|
+
|
|
1414
|
+
option go_package = "github.com/your-org/your-project/api/user/v1;userv1";
|
|
1415
|
+
|
|
1416
|
+
service UserService {
|
|
1417
|
+
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
|
|
1418
|
+
rpc GetUser(GetUserRequest) returns (GetUserResponse);
|
|
1419
|
+
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
|
|
1420
|
+
rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse);
|
|
1421
|
+
rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse);
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
message User {
|
|
1425
|
+
string id = 1;
|
|
1426
|
+
string email = 2;
|
|
1427
|
+
string name = 3;
|
|
1428
|
+
google.protobuf.Timestamp created_at = 4;
|
|
1429
|
+
google.protobuf.Timestamp updated_at = 5;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
message CreateUserRequest {
|
|
1433
|
+
string email = 1;
|
|
1434
|
+
string name = 2;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
message CreateUserResponse {
|
|
1438
|
+
User user = 1;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
// server/grpc/user_service.go
|
|
1442
|
+
package grpc
|
|
1443
|
+
|
|
1444
|
+
import (
|
|
1445
|
+
"context"
|
|
1446
|
+
|
|
1447
|
+
"google.golang.org/grpc/codes"
|
|
1448
|
+
"google.golang.org/grpc/status"
|
|
1449
|
+
"google.golang.org/protobuf/types/known/timestamppb"
|
|
1450
|
+
|
|
1451
|
+
"github.com/your-org/your-project/api/user/v1"
|
|
1452
|
+
"github.com/your-org/your-project/internal/service"
|
|
1453
|
+
)
|
|
1454
|
+
|
|
1455
|
+
type GRPCUserServer struct {
|
|
1456
|
+
userv1.UnimplementedUserServiceServer
|
|
1457
|
+
userService service.UserService
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
func NewGRPCUserServer(userService service.UserService) *GRPCUserServer {
|
|
1461
|
+
return &GRPCUserServer{
|
|
1462
|
+
userService: userService,
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
func (s *GRPCUserServer) CreateUser(ctx context.Context, req *userv1.CreateUserRequest) (*userv1.CreateUserResponse, error) {
|
|
1467
|
+
// Convert protobuf to domain model
|
|
1468
|
+
createReq := model.CreateUserRequest{
|
|
1469
|
+
Email: req.GetEmail(),
|
|
1470
|
+
Name: req.GetName(),
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
// Call service layer
|
|
1474
|
+
user, err := s.userService.CreateUser(ctx, createReq)
|
|
1475
|
+
if err != nil {
|
|
1476
|
+
if errors.Is(err, service.ErrUserExists) {
|
|
1477
|
+
return nil, status.Error(codes.AlreadyExists, "user already exists")
|
|
1478
|
+
}
|
|
1479
|
+
if errors.Is(err, service.ErrInvalidInput) {
|
|
1480
|
+
return nil, status.Error(codes.InvalidArgument, err.Error())
|
|
1481
|
+
}
|
|
1482
|
+
return nil, status.Error(codes.Internal, "internal server error")
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
// Convert domain model to protobuf
|
|
1486
|
+
return &userv1.CreateUserResponse{
|
|
1487
|
+
User: s.userToProto(user),
|
|
1488
|
+
}, nil
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
func (s *GRPCUserServer) GetUser(ctx context.Context, req *userv1.GetUserRequest) (*userv1.GetUserResponse, error) {
|
|
1492
|
+
user, err := s.userService.GetUser(ctx, req.GetId())
|
|
1493
|
+
if err != nil {
|
|
1494
|
+
if errors.Is(err, service.ErrUserNotFound) {
|
|
1495
|
+
return nil, status.Error(codes.NotFound, "user not found")
|
|
1496
|
+
}
|
|
1497
|
+
return nil, status.Error(codes.Internal, "internal server error")
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
return &userv1.GetUserResponse{
|
|
1501
|
+
User: s.userToProto(user),
|
|
1502
|
+
}, nil
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
func (s *GRPCUserServer) userToProto(user *model.User) *userv1.User {
|
|
1506
|
+
return &userv1.User{
|
|
1507
|
+
Id: user.ID,
|
|
1508
|
+
Email: user.Email,
|
|
1509
|
+
Name: user.Name,
|
|
1510
|
+
CreatedAt: timestamppb.New(user.CreatedAt),
|
|
1511
|
+
UpdatedAt: timestamppb.New(user.UpdatedAt),
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
```
|
|
1515
|
+
|
|
1516
|
+
### Message Queue Integration
|
|
1517
|
+
|
|
1518
|
+
```go
|
|
1519
|
+
// pkg/messaging/nats.go
|
|
1520
|
+
package messaging
|
|
1521
|
+
|
|
1522
|
+
import (
|
|
1523
|
+
"context"
|
|
1524
|
+
"encoding/json"
|
|
1525
|
+
"fmt"
|
|
1526
|
+
"time"
|
|
1527
|
+
|
|
1528
|
+
"github.com/nats-io/nats.go"
|
|
1529
|
+
)
|
|
1530
|
+
|
|
1531
|
+
// Message represents a message payload
|
|
1532
|
+
type Message struct {
|
|
1533
|
+
ID string `json:"id"`
|
|
1534
|
+
Type string `json:"type"`
|
|
1535
|
+
Data map[string]interface{} `json:"data"`
|
|
1536
|
+
Timestamp time.Time `json:"timestamp"`
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// MessageHandler handles incoming messages
|
|
1540
|
+
type MessageHandler func(ctx context.Context, msg *Message) error
|
|
1541
|
+
|
|
1542
|
+
// MessageBroker defines message broker interface
|
|
1543
|
+
type MessageBroker interface {
|
|
1544
|
+
Publish(ctx context.Context, subject string, msg *Message) error
|
|
1545
|
+
Subscribe(ctx context.Context, subject string, handler MessageHandler) error
|
|
1546
|
+
Close() error
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
// NATSMessageBroker implements MessageBroker using NATS
|
|
1550
|
+
type NATSMessageBroker struct {
|
|
1551
|
+
conn *nats.Conn
|
|
1552
|
+
js nats.JetStreamContext
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
func NewNATSMessageBroker(url string) (*NATSMessageBroker, error) {
|
|
1556
|
+
conn, err := nats.Connect(url,
|
|
1557
|
+
nats.ReconnectWait(2*time.Second),
|
|
1558
|
+
nats.MaxReconnects(5),
|
|
1559
|
+
nats.DisconnectErrHandler(func(nc *nats.Conn, err error) {
|
|
1560
|
+
fmt.Printf("NATS disconnected: %v\n", err)
|
|
1561
|
+
}),
|
|
1562
|
+
nats.ReconnectHandler(func(nc *nats.Conn) {
|
|
1563
|
+
fmt.Printf("NATS reconnected to %v\n", nc.ConnectedUrl())
|
|
1564
|
+
}),
|
|
1565
|
+
)
|
|
1566
|
+
if err != nil {
|
|
1567
|
+
return nil, fmt.Errorf("failed to connect to NATS: %w", err)
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
js, err := conn.JetStream()
|
|
1571
|
+
if err != nil {
|
|
1572
|
+
return nil, fmt.Errorf("failed to get JetStream context: %w", err)
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
return &NATSMessageBroker{
|
|
1576
|
+
conn: conn,
|
|
1577
|
+
js: js,
|
|
1578
|
+
}, nil
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
func (b *NATSMessageBroker) Publish(ctx context.Context, subject string, msg *Message) error {
|
|
1582
|
+
data, err := json.Marshal(msg)
|
|
1583
|
+
if err != nil {
|
|
1584
|
+
return fmt.Errorf("failed to marshal message: %w", err)
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
// Use JetStream for persistent messaging
|
|
1588
|
+
_, err = b.js.Publish(ctx, subject, data)
|
|
1589
|
+
if err != nil {
|
|
1590
|
+
return fmt.Errorf("failed to publish message: %w", err)
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
return nil
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
func (b *NATSMessageBroker) Subscribe(ctx context.Context, subject string, handler MessageHandler) error {
|
|
1597
|
+
// Create stream if it doesn't exist
|
|
1598
|
+
streamConfig := &nats.StreamConfig{
|
|
1599
|
+
Name: "events",
|
|
1600
|
+
Subjects: []string{subject},
|
|
1601
|
+
Retention: nats.WorkQueuePolicy,
|
|
1602
|
+
MaxBytes: 1024 * 1024 * 1024, // 1GB
|
|
1603
|
+
Storage: nats.FileStorage,
|
|
1604
|
+
Replicas: 1,
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
_, err := b.js.AddStream(streamConfig)
|
|
1608
|
+
if err != nil && !errors.Is(err, nats.ErrStreamNameAlreadyInUse) {
|
|
1609
|
+
return fmt.Errorf("failed to create stream: %w", err)
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
// Create consumer
|
|
1613
|
+
consumerConfig := &nats.ConsumerConfig{
|
|
1614
|
+
Durable: "worker",
|
|
1615
|
+
AckPolicy: nats.AckExplicitPolicy,
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
_, err = b.js.AddConsumer("events", consumerConfig)
|
|
1619
|
+
if err != nil && !errors.Is(err, nats.ErrConsumerAlreadyExists) {
|
|
1620
|
+
return fmt.Errorf("failed to create consumer: %w", err)
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
// Subscribe to messages
|
|
1624
|
+
_, err = b.js.Subscribe(subject, func(msg *nats.Msg) {
|
|
1625
|
+
var message Message
|
|
1626
|
+
if err := json.Unmarshal(msg.Data, &message); err != nil {
|
|
1627
|
+
fmt.Printf("Failed to unmarshal message: %v\n", err)
|
|
1628
|
+
msg.Nak()
|
|
1629
|
+
return
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
if err := handler(ctx, &message); err != nil {
|
|
1633
|
+
fmt.Printf("Handler failed: %v\n", err)
|
|
1634
|
+
msg.Nak()
|
|
1635
|
+
return
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
msg.Ack()
|
|
1639
|
+
}, nats.Durable("worker"))
|
|
1640
|
+
|
|
1641
|
+
if err != nil {
|
|
1642
|
+
return fmt.Errorf("failed to subscribe: %w", err)
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
return nil
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
func (b *NATSMessageBroker) Close() error {
|
|
1649
|
+
b.conn.Close()
|
|
1650
|
+
return nil
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
// Usage example
|
|
1654
|
+
func setupEventHandlers(broker MessageBroker) {
|
|
1655
|
+
// User created event handler
|
|
1656
|
+
broker.Subscribe(context.Background(), "user.created", func(ctx context.Context, msg *Message) error {
|
|
1657
|
+
fmt.Printf("User created: %v\n", msg)
|
|
1658
|
+
// Send welcome email, update analytics, etc.
|
|
1659
|
+
return nil
|
|
1660
|
+
})
|
|
1661
|
+
|
|
1662
|
+
// User updated event handler
|
|
1663
|
+
broker.Subscribe(context.Background(), "user.updated", func(ctx context.Context, msg *Message) error {
|
|
1664
|
+
fmt.Printf("User updated: %v\n", msg)
|
|
1665
|
+
// Update search index, notify other services, etc.
|
|
1666
|
+
return nil
|
|
1667
|
+
})
|
|
1668
|
+
}
|
|
1669
|
+
```
|
|
1670
|
+
|
|
1671
|
+
## Modern Development Workflow
|
|
1672
|
+
|
|
1673
|
+
### Configuration Management
|
|
1674
|
+
|
|
1675
|
+
```go
|
|
1676
|
+
// pkg/config/config.go
|
|
1677
|
+
package config
|
|
1678
|
+
|
|
1679
|
+
import (
|
|
1680
|
+
"fmt"
|
|
1681
|
+
"os"
|
|
1682
|
+
"strconv"
|
|
1683
|
+
"time"
|
|
1684
|
+
|
|
1685
|
+
"github.com/spf13/viper"
|
|
1686
|
+
)
|
|
1687
|
+
|
|
1688
|
+
// Config holds application configuration
|
|
1689
|
+
type Config struct {
|
|
1690
|
+
Server ServerConfig `mapstructure:"server"`
|
|
1691
|
+
Database DatabaseConfig `mapstructure:"database"`
|
|
1692
|
+
Redis RedisConfig `mapstructure:"redis"`
|
|
1693
|
+
Auth AuthConfig `mapstructure:"auth"`
|
|
1694
|
+
Logging LoggingConfig `mapstructure:"logging"`
|
|
1695
|
+
Monitoring MonitoringConfig `mapstructure:"monitoring"`
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
// ServerConfig holds server configuration
|
|
1699
|
+
type ServerConfig struct {
|
|
1700
|
+
Host string `mapstructure:"host"`
|
|
1701
|
+
Port int `mapstructure:"port"`
|
|
1702
|
+
ReadTimeout time.Duration `mapstructure:"read_timeout"`
|
|
1703
|
+
WriteTimeout time.Duration `mapstructure:"write_timeout"`
|
|
1704
|
+
GracefulShutdownTimeout time.Duration `mapstructure:"graceful_shutdown_timeout"`
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
// DatabaseConfig holds database configuration
|
|
1708
|
+
type DatabaseConfig struct {
|
|
1709
|
+
Host string `mapstructure:"host"`
|
|
1710
|
+
Port int `mapstructure:"port"`
|
|
1711
|
+
User string `mapstructure:"user"`
|
|
1712
|
+
Password string `mapstructure:"password"`
|
|
1713
|
+
DBName string `mapstructure:"db_name"`
|
|
1714
|
+
SSLMode string `mapstructure:"ssl_mode"`
|
|
1715
|
+
MaxOpenConns int `mapstructure:"max_open_conns"`
|
|
1716
|
+
MaxIdleConns int `mapstructure:"max_idle_conns"`
|
|
1717
|
+
ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"`
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
// Load loads configuration from file and environment variables
|
|
1721
|
+
func Load() (*Config, error) {
|
|
1722
|
+
// Set default values
|
|
1723
|
+
setDefaults()
|
|
1724
|
+
|
|
1725
|
+
// Load from config file
|
|
1726
|
+
viper.SetConfigName("config")
|
|
1727
|
+
viper.SetConfigType("yaml")
|
|
1728
|
+
viper.AddConfigPath("./configs")
|
|
1729
|
+
viper.AddConfigPath(".")
|
|
1730
|
+
|
|
1731
|
+
// Load environment variables
|
|
1732
|
+
viper.AutomaticEnv()
|
|
1733
|
+
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
|
1734
|
+
|
|
1735
|
+
if err := viper.ReadInConfig(); err != nil {
|
|
1736
|
+
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
|
|
1737
|
+
return nil, fmt.Errorf("failed to read config file: %w", err)
|
|
1738
|
+
}
|
|
1739
|
+
// Config file not found is OK, we'll use defaults and env vars
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
var config Config
|
|
1743
|
+
if err := viper.Unmarshal(&config); err != nil {
|
|
1744
|
+
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
// Validate configuration
|
|
1748
|
+
if err := validate(&config); err != nil {
|
|
1749
|
+
return nil, fmt.Errorf("invalid configuration: %w", err)
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
return &config, nil
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
func setDefaults() {
|
|
1756
|
+
viper.SetDefault("server.host", "0.0.0.0")
|
|
1757
|
+
viper.SetDefault("server.port", 8080)
|
|
1758
|
+
viper.SetDefault("server.read_timeout", 30*time.Second)
|
|
1759
|
+
viper.SetDefault("server.write_timeout", 30*time.Second)
|
|
1760
|
+
viper.SetDefault("server.graceful_shutdown_timeout", 30*time.Second)
|
|
1761
|
+
|
|
1762
|
+
viper.SetDefault("database.host", "localhost")
|
|
1763
|
+
viper.SetDefault("database.port", 5432)
|
|
1764
|
+
viper.SetDefault("database.ssl_mode", "disable")
|
|
1765
|
+
viper.SetDefault("database.max_open_conns", 25)
|
|
1766
|
+
viper.SetDefault("database.max_idle_conns", 25)
|
|
1767
|
+
viper.SetDefault("database.conn_max_lifetime", 5*time.Minute)
|
|
1768
|
+
|
|
1769
|
+
viper.SetDefault("redis.host", "localhost")
|
|
1770
|
+
viper.SetDefault("redis.port", 6379)
|
|
1771
|
+
viper.SetDefault("redis.db", 0)
|
|
1772
|
+
|
|
1773
|
+
viper.SetDefault("auth.jwt.expiration", 24*time.Hour)
|
|
1774
|
+
|
|
1775
|
+
viper.SetDefault("logging.level", "info")
|
|
1776
|
+
viper.SetDefault("logging.format", "json")
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
func validate(config *Config) error {
|
|
1780
|
+
if config.Server.Port <= 0 || config.Server.Port > 65535 {
|
|
1781
|
+
return fmt.Errorf("invalid server port: %d", config.Server.Port)
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
if config.Database.Host == "" {
|
|
1785
|
+
return fmt.Errorf("database host is required")
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
if config.Database.User == "" {
|
|
1789
|
+
return fmt.Errorf("database user is required")
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
return nil
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
// GetDSN returns database connection string
|
|
1796
|
+
func (c *DatabaseConfig) GetDSN() string {
|
|
1797
|
+
return fmt.Sprintf(
|
|
1798
|
+
"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
|
|
1799
|
+
c.Host, c.Port, c.User, c.Password, c.DBName, c.SSLMode,
|
|
1800
|
+
)
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
// GetRedisAddr returns Redis connection address
|
|
1804
|
+
func (c *RedisConfig) GetAddr() string {
|
|
1805
|
+
return fmt.Sprintf("%s:%d", c.Host, c.Port)
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
// GetServerAddr returns server address
|
|
1809
|
+
func (c *ServerConfig) GetAddr() string {
|
|
1810
|
+
return fmt.Sprintf("%s:%d", c.Host, c.Port)
|
|
1811
|
+
}
|
|
1812
|
+
```
|
|
1813
|
+
|
|
1814
|
+
### Makefile for Development
|
|
1815
|
+
|
|
1816
|
+
```makefile
|
|
1817
|
+
# Makefile
|
|
1818
|
+
.PHONY: help build run test clean lint format docker-build docker-run
|
|
1819
|
+
|
|
1820
|
+
# Variables
|
|
1821
|
+
APP_NAME := your-app
|
|
1822
|
+
VERSION := $(shell git describe --tags --always --dirty)
|
|
1823
|
+
BUILD_TIME := $(shell date -u '+%Y-%m-%d_%H:%M:%S')
|
|
1824
|
+
LDFLAGS := -ldflags "-X main.Version=$(VERSION) -X main.BuildTime=$(BUILD_TIME)"
|
|
1825
|
+
|
|
1826
|
+
# Docker variables
|
|
1827
|
+
DOCKER_REGISTRY := your-registry
|
|
1828
|
+
DOCKER_TAG := $(DOCKER_REGISTRY)/$(APP_NAME):$(VERSION)
|
|
1829
|
+
|
|
1830
|
+
# Help
|
|
1831
|
+
help:
|
|
1832
|
+
@echo "Available commands:"
|
|
1833
|
+
@echo " build Build the application"
|
|
1834
|
+
@echo " run Run the application"
|
|
1835
|
+
@echo " test Run tests"
|
|
1836
|
+
@echo " test-coverage Run tests with coverage"
|
|
1837
|
+
@echo " lint Run linter"
|
|
1838
|
+
@echo " format Format code"
|
|
1839
|
+
@echo " clean Clean build artifacts"
|
|
1840
|
+
@echo " deps Install dependencies"
|
|
1841
|
+
@echo " migrate Run database migrations"
|
|
1842
|
+
@echo " docker-build Build Docker image"
|
|
1843
|
+
@echo " docker-run Run Docker container"
|
|
1844
|
+
@echo " generate Generate code (mocks, protobuf, etc.)"
|
|
1845
|
+
|
|
1846
|
+
# Build
|
|
1847
|
+
build:
|
|
1848
|
+
go build $(LDFLAGS) -o bin/$(APP_NAME) cmd/server/main.go
|
|
1849
|
+
|
|
1850
|
+
# Run
|
|
1851
|
+
run:
|
|
1852
|
+
go run $(LDFLAGS) cmd/server/main.go
|
|
1853
|
+
|
|
1854
|
+
# Development mode with hot reload
|
|
1855
|
+
dev:
|
|
1856
|
+
air
|
|
1857
|
+
|
|
1858
|
+
# Tests
|
|
1859
|
+
test:
|
|
1860
|
+
go test -v ./...
|
|
1861
|
+
|
|
1862
|
+
test-coverage:
|
|
1863
|
+
go test -v -race -coverprofile=coverage.out ./...
|
|
1864
|
+
go tool cover -html=coverage.out -o coverage.html
|
|
1865
|
+
|
|
1866
|
+
test-bench:
|
|
1867
|
+
go test -bench=. -benchmem ./...
|
|
1868
|
+
|
|
1869
|
+
# Linting
|
|
1870
|
+
lint:
|
|
1871
|
+
golangci-lint run
|
|
1872
|
+
|
|
1873
|
+
# Formatting
|
|
1874
|
+
format:
|
|
1875
|
+
go fmt ./...
|
|
1876
|
+
goimports -w .
|
|
1877
|
+
|
|
1878
|
+
# Dependencies
|
|
1879
|
+
deps:
|
|
1880
|
+
go mod download
|
|
1881
|
+
go mod tidy
|
|
1882
|
+
|
|
1883
|
+
# Generate code
|
|
1884
|
+
generate:
|
|
1885
|
+
go generate ./...
|
|
1886
|
+
mockgen -source=internal/service/user.go -destination=test/mocks/user_service_mock.go
|
|
1887
|
+
protoc --go_out=. --go-grpc_out=. api/proto/user.proto
|
|
1888
|
+
|
|
1889
|
+
# Database
|
|
1890
|
+
migrate-up:
|
|
1891
|
+
migrate -path migrations -database "$(shell grep -A5 'database:' configs/config.yaml | tail -n1 | cut -d' ' -f2)" up
|
|
1892
|
+
|
|
1893
|
+
migrate-down:
|
|
1894
|
+
migrate -path migrations -database "$(shell grep -A5 'database:' configs/config.yaml | tail -n1 | cut -d' ' -f2)" down
|
|
1895
|
+
|
|
1896
|
+
# Clean
|
|
1897
|
+
clean:
|
|
1898
|
+
rm -rf bin/
|
|
1899
|
+
rm -f coverage.out coverage.html
|
|
1900
|
+
|
|
1901
|
+
# Docker
|
|
1902
|
+
docker-build:
|
|
1903
|
+
docker build -t $(DOCKER_TAG) .
|
|
1904
|
+
|
|
1905
|
+
docker-run:
|
|
1906
|
+
docker run -p 8080:8080 $(DOCKER_TAG)
|
|
1907
|
+
|
|
1908
|
+
docker-push:
|
|
1909
|
+
docker push $(DOCKER_TAG)
|
|
1910
|
+
|
|
1911
|
+
# Install tools
|
|
1912
|
+
install-tools:
|
|
1913
|
+
go install github.com/cosmtrek/air@latest
|
|
1914
|
+
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
|
1915
|
+
go install golang.org/x/tools/cmd/goimports@latest
|
|
1916
|
+
go install github.com/golang/mock/mockgen@latest
|
|
1917
|
+
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
|
1918
|
+
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
|
1919
|
+
```
|
|
1920
|
+
|
|
1921
|
+
---
|
|
93
1922
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
1923
|
+
**Created by**: MoAI Language Skill Factory
|
|
1924
|
+
**Last Updated**: 2025-11-06
|
|
1925
|
+
**Version**: 2.0.0
|
|
1926
|
+
**Go Target**: 1.25+ with latest language features
|
|
97
1927
|
|
|
98
|
-
|
|
99
|
-
- Enable automatic validation by matching your linter with the language's official style guide.
|
|
100
|
-
- Fix test/build pipelines with reproducible commands in CI.
|
|
1928
|
+
This skill provides comprehensive Go development guidance with 2025 best practices, covering everything from basic concurrent programming to advanced cloud-native patterns and enterprise-grade applications.
|