javi-forge 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ci-local/ci-local.sh +20 -8
- package/package.json +1 -1
- package/ai-config/.skillignore +0 -15
- package/ai-config/AUTO_INVOKE.md +0 -300
- package/ai-config/agents/_TEMPLATE.md +0 -93
- package/ai-config/agents/business/api-designer.md +0 -1657
- package/ai-config/agents/business/business-analyst.md +0 -1331
- package/ai-config/agents/business/product-strategist.md +0 -206
- package/ai-config/agents/business/project-manager.md +0 -178
- package/ai-config/agents/business/requirements-analyst.md +0 -1277
- package/ai-config/agents/business/technical-writer.md +0 -1679
- package/ai-config/agents/creative/ux-designer.md +0 -205
- package/ai-config/agents/data-ai/ai-engineer.md +0 -487
- package/ai-config/agents/data-ai/analytics-engineer.md +0 -953
- package/ai-config/agents/data-ai/data-engineer.md +0 -173
- package/ai-config/agents/data-ai/data-scientist.md +0 -672
- package/ai-config/agents/data-ai/mlops-engineer.md +0 -814
- package/ai-config/agents/data-ai/prompt-engineer.md +0 -772
- package/ai-config/agents/development/angular-expert.md +0 -620
- package/ai-config/agents/development/backend-architect.md +0 -795
- package/ai-config/agents/development/database-specialist.md +0 -212
- package/ai-config/agents/development/frontend-specialist.md +0 -686
- package/ai-config/agents/development/fullstack-engineer.md +0 -668
- package/ai-config/agents/development/golang-pro.md +0 -338
- package/ai-config/agents/development/java-enterprise.md +0 -400
- package/ai-config/agents/development/javascript-pro.md +0 -422
- package/ai-config/agents/development/nextjs-pro.md +0 -474
- package/ai-config/agents/development/python-pro.md +0 -570
- package/ai-config/agents/development/react-pro.md +0 -487
- package/ai-config/agents/development/rust-pro.md +0 -246
- package/ai-config/agents/development/spring-boot-4-expert.md +0 -326
- package/ai-config/agents/development/typescript-pro.md +0 -336
- package/ai-config/agents/development/vue-specialist.md +0 -605
- package/ai-config/agents/infrastructure/cloud-architect.md +0 -472
- package/ai-config/agents/infrastructure/deployment-manager.md +0 -358
- package/ai-config/agents/infrastructure/devops-engineer.md +0 -455
- package/ai-config/agents/infrastructure/incident-responder.md +0 -519
- package/ai-config/agents/infrastructure/kubernetes-expert.md +0 -705
- package/ai-config/agents/infrastructure/monitoring-specialist.md +0 -674
- package/ai-config/agents/infrastructure/performance-engineer.md +0 -658
- package/ai-config/agents/orchestrator.md +0 -241
- package/ai-config/agents/quality/accessibility-auditor.md +0 -1204
- package/ai-config/agents/quality/code-reviewer-compact.md +0 -123
- package/ai-config/agents/quality/code-reviewer.md +0 -363
- package/ai-config/agents/quality/dependency-manager.md +0 -743
- package/ai-config/agents/quality/e2e-test-specialist.md +0 -1005
- package/ai-config/agents/quality/performance-tester.md +0 -1086
- package/ai-config/agents/quality/security-auditor.md +0 -133
- package/ai-config/agents/quality/test-engineer.md +0 -453
- package/ai-config/agents/specialists/api-designer.md +0 -87
- package/ai-config/agents/specialists/backend-architect.md +0 -73
- package/ai-config/agents/specialists/code-reviewer.md +0 -77
- package/ai-config/agents/specialists/db-optimizer.md +0 -75
- package/ai-config/agents/specialists/devops-engineer.md +0 -83
- package/ai-config/agents/specialists/documentation-writer.md +0 -78
- package/ai-config/agents/specialists/frontend-developer.md +0 -75
- package/ai-config/agents/specialists/performance-analyst.md +0 -82
- package/ai-config/agents/specialists/refactor-specialist.md +0 -74
- package/ai-config/agents/specialists/security-auditor.md +0 -74
- package/ai-config/agents/specialists/test-engineer.md +0 -81
- package/ai-config/agents/specialists/ux-consultant.md +0 -76
- package/ai-config/agents/specialized/agent-generator.md +0 -1190
- package/ai-config/agents/specialized/blockchain-developer.md +0 -149
- package/ai-config/agents/specialized/code-migrator.md +0 -892
- package/ai-config/agents/specialized/context-manager.md +0 -978
- package/ai-config/agents/specialized/documentation-writer.md +0 -1078
- package/ai-config/agents/specialized/ecommerce-expert.md +0 -1756
- package/ai-config/agents/specialized/embedded-engineer.md +0 -1714
- package/ai-config/agents/specialized/error-detective.md +0 -1034
- package/ai-config/agents/specialized/fintech-specialist.md +0 -1659
- package/ai-config/agents/specialized/freelance-project-planner-v2.md +0 -1988
- package/ai-config/agents/specialized/freelance-project-planner-v3.md +0 -2136
- package/ai-config/agents/specialized/freelance-project-planner-v4.md +0 -4503
- package/ai-config/agents/specialized/freelance-project-planner.md +0 -722
- package/ai-config/agents/specialized/game-developer.md +0 -1963
- package/ai-config/agents/specialized/healthcare-dev.md +0 -1620
- package/ai-config/agents/specialized/mobile-developer.md +0 -188
- package/ai-config/agents/specialized/parallel-plan-executor.md +0 -506
- package/ai-config/agents/specialized/plan-executor.md +0 -485
- package/ai-config/agents/specialized/solo-dev-planner-modular/00-INDEX.md +0 -485
- package/ai-config/agents/specialized/solo-dev-planner-modular/01-CORE.md +0 -3493
- package/ai-config/agents/specialized/solo-dev-planner-modular/02-SELF-CORRECTION.md +0 -778
- package/ai-config/agents/specialized/solo-dev-planner-modular/03-PROGRESSIVE-SETUP.md +0 -918
- package/ai-config/agents/specialized/solo-dev-planner-modular/04-DEPLOYMENT.md +0 -1537
- package/ai-config/agents/specialized/solo-dev-planner-modular/05-TESTING.md +0 -2633
- package/ai-config/agents/specialized/solo-dev-planner-modular/06-OPERATIONS.md +0 -5610
- package/ai-config/agents/specialized/solo-dev-planner-modular/INSTALL.md +0 -335
- package/ai-config/agents/specialized/solo-dev-planner-modular/QUICK-REFERENCE.txt +0 -215
- package/ai-config/agents/specialized/solo-dev-planner-modular/README.md +0 -260
- package/ai-config/agents/specialized/solo-dev-planner-modular/START-HERE.md +0 -379
- package/ai-config/agents/specialized/solo-dev-planner-modular/WORKFLOW-DIAGRAM.md +0 -355
- package/ai-config/agents/specialized/solo-dev-planner-modular/solo-dev-planner.md +0 -279
- package/ai-config/agents/specialized/template-writer.md +0 -347
- package/ai-config/agents/specialized/test-runner.md +0 -99
- package/ai-config/agents/specialized/vibekanban-smart-worker.md +0 -244
- package/ai-config/agents/specialized/wave-executor.md +0 -138
- package/ai-config/agents/specialized/workflow-optimizer.md +0 -1114
- package/ai-config/commands/git/changelog.md +0 -32
- package/ai-config/commands/git/ci-local.md +0 -70
- package/ai-config/commands/git/commit.md +0 -35
- package/ai-config/commands/git/fix-issue.md +0 -23
- package/ai-config/commands/git/pr-create.md +0 -42
- package/ai-config/commands/git/pr-review.md +0 -50
- package/ai-config/commands/git/worktree.md +0 -39
- package/ai-config/commands/refactoring/cleanup.md +0 -24
- package/ai-config/commands/refactoring/dead-code.md +0 -40
- package/ai-config/commands/refactoring/extract.md +0 -31
- package/ai-config/commands/testing/e2e.md +0 -30
- package/ai-config/commands/testing/tdd.md +0 -36
- package/ai-config/commands/testing/test-coverage.md +0 -30
- package/ai-config/commands/testing/test-fix.md +0 -24
- package/ai-config/commands/workflow/generate-agents-md.md +0 -85
- package/ai-config/commands/workflow/planning.md +0 -47
- package/ai-config/commands/workflows/compound.md +0 -89
- package/ai-config/commands/workflows/diagnose.md +0 -70
- package/ai-config/commands/workflows/discover.md +0 -86
- package/ai-config/commands/workflows/plan.md +0 -77
- package/ai-config/commands/workflows/review.md +0 -78
- package/ai-config/commands/workflows/work.md +0 -75
- package/ai-config/config.yaml +0 -18
- package/ai-config/hooks/_TEMPLATE.md +0 -96
- package/ai-config/hooks/block-dangerous-commands.md +0 -75
- package/ai-config/hooks/commit-guard.md +0 -90
- package/ai-config/hooks/context-loader.md +0 -73
- package/ai-config/hooks/improve-prompt.md +0 -91
- package/ai-config/hooks/learning-log.md +0 -72
- package/ai-config/hooks/model-router.md +0 -86
- package/ai-config/hooks/secret-scanner.md +0 -64
- package/ai-config/hooks/skill-validator.md +0 -102
- package/ai-config/hooks/task-artifact.md +0 -114
- package/ai-config/hooks/validate-workflow.md +0 -100
- package/ai-config/prompts/base.md +0 -71
- package/ai-config/prompts/modes/debug.md +0 -34
- package/ai-config/prompts/modes/deploy.md +0 -40
- package/ai-config/prompts/modes/research.md +0 -32
- package/ai-config/prompts/modes/review.md +0 -33
- package/ai-config/prompts/review-policy.md +0 -79
- package/ai-config/skills/_TEMPLATE.md +0 -157
- package/ai-config/skills/backend/api-gateway/SKILL.md +0 -254
- package/ai-config/skills/backend/bff-concepts/SKILL.md +0 -239
- package/ai-config/skills/backend/bff-spring/SKILL.md +0 -364
- package/ai-config/skills/backend/chi-router/SKILL.md +0 -396
- package/ai-config/skills/backend/error-handling/SKILL.md +0 -255
- package/ai-config/skills/backend/exceptions-spring/SKILL.md +0 -323
- package/ai-config/skills/backend/fastapi/SKILL.md +0 -302
- package/ai-config/skills/backend/gateway-spring/SKILL.md +0 -390
- package/ai-config/skills/backend/go-backend/SKILL.md +0 -457
- package/ai-config/skills/backend/gradle-multimodule/SKILL.md +0 -274
- package/ai-config/skills/backend/graphql-concepts/SKILL.md +0 -352
- package/ai-config/skills/backend/graphql-spring/SKILL.md +0 -398
- package/ai-config/skills/backend/grpc-concepts/SKILL.md +0 -283
- package/ai-config/skills/backend/grpc-spring/SKILL.md +0 -445
- package/ai-config/skills/backend/jwt-auth/SKILL.md +0 -412
- package/ai-config/skills/backend/notifications-concepts/SKILL.md +0 -259
- package/ai-config/skills/backend/recommendations-concepts/SKILL.md +0 -261
- package/ai-config/skills/backend/search-concepts/SKILL.md +0 -263
- package/ai-config/skills/backend/search-spring/SKILL.md +0 -375
- package/ai-config/skills/backend/spring-boot-4/SKILL.md +0 -172
- package/ai-config/skills/backend/websockets/SKILL.md +0 -532
- package/ai-config/skills/data-ai/ai-ml/SKILL.md +0 -423
- package/ai-config/skills/data-ai/analytics-concepts/SKILL.md +0 -195
- package/ai-config/skills/data-ai/analytics-spring/SKILL.md +0 -340
- package/ai-config/skills/data-ai/duckdb-analytics/SKILL.md +0 -440
- package/ai-config/skills/data-ai/langchain/SKILL.md +0 -238
- package/ai-config/skills/data-ai/mlflow/SKILL.md +0 -302
- package/ai-config/skills/data-ai/onnx-inference/SKILL.md +0 -290
- package/ai-config/skills/data-ai/powerbi/SKILL.md +0 -352
- package/ai-config/skills/data-ai/pytorch/SKILL.md +0 -274
- package/ai-config/skills/data-ai/scikit-learn/SKILL.md +0 -321
- package/ai-config/skills/data-ai/vector-db/SKILL.md +0 -301
- package/ai-config/skills/database/graph-databases/SKILL.md +0 -218
- package/ai-config/skills/database/graph-spring/SKILL.md +0 -361
- package/ai-config/skills/database/pgx-postgres/SKILL.md +0 -512
- package/ai-config/skills/database/redis-cache/SKILL.md +0 -343
- package/ai-config/skills/database/sqlite-embedded/SKILL.md +0 -388
- package/ai-config/skills/database/timescaledb/SKILL.md +0 -320
- package/ai-config/skills/docs/api-documentation/SKILL.md +0 -293
- package/ai-config/skills/docs/docs-spring/SKILL.md +0 -377
- package/ai-config/skills/docs/mustache-templates/SKILL.md +0 -190
- package/ai-config/skills/docs/technical-docs/SKILL.md +0 -447
- package/ai-config/skills/frontend/astro-ssr/SKILL.md +0 -441
- package/ai-config/skills/frontend/frontend-design/SKILL.md +0 -54
- package/ai-config/skills/frontend/frontend-web/SKILL.md +0 -368
- package/ai-config/skills/frontend/mantine-ui/SKILL.md +0 -396
- package/ai-config/skills/frontend/tanstack-query/SKILL.md +0 -439
- package/ai-config/skills/frontend/zod-validation/SKILL.md +0 -417
- package/ai-config/skills/frontend/zustand-state/SKILL.md +0 -350
- package/ai-config/skills/infrastructure/chaos-engineering/SKILL.md +0 -244
- package/ai-config/skills/infrastructure/chaos-spring/SKILL.md +0 -378
- package/ai-config/skills/infrastructure/devops-infra/SKILL.md +0 -435
- package/ai-config/skills/infrastructure/docker-containers/SKILL.md +0 -420
- package/ai-config/skills/infrastructure/kubernetes/SKILL.md +0 -456
- package/ai-config/skills/infrastructure/opentelemetry/SKILL.md +0 -546
- package/ai-config/skills/infrastructure/traefik-proxy/SKILL.md +0 -474
- package/ai-config/skills/infrastructure/woodpecker-ci/SKILL.md +0 -315
- package/ai-config/skills/mobile/ionic-capacitor/SKILL.md +0 -504
- package/ai-config/skills/mobile/mobile-ionic/SKILL.md +0 -448
- package/ai-config/skills/prompt-improver/SKILL.md +0 -125
- package/ai-config/skills/quality/ghagga-review/SKILL.md +0 -216
- package/ai-config/skills/references/hooks-patterns/SKILL.md +0 -238
- package/ai-config/skills/references/mcp-servers/SKILL.md +0 -275
- package/ai-config/skills/references/plugins-reference/SKILL.md +0 -110
- package/ai-config/skills/references/skills-reference/SKILL.md +0 -420
- package/ai-config/skills/references/subagent-templates/SKILL.md +0 -193
- package/ai-config/skills/systems-iot/modbus-protocol/SKILL.md +0 -410
- package/ai-config/skills/systems-iot/mqtt-rumqttc/SKILL.md +0 -408
- package/ai-config/skills/systems-iot/rust-systems/SKILL.md +0 -386
- package/ai-config/skills/systems-iot/tokio-async/SKILL.md +0 -324
- package/ai-config/skills/testing/playwright-e2e/SKILL.md +0 -289
- package/ai-config/skills/testing/testcontainers/SKILL.md +0 -299
- package/ai-config/skills/testing/vitest-testing/SKILL.md +0 -381
- package/ai-config/skills/workflow/ci-local-guide/SKILL.md +0 -118
- package/ai-config/skills/workflow/claude-automation-recommender/SKILL.md +0 -299
- package/ai-config/skills/workflow/claude-md-improver/SKILL.md +0 -158
- package/ai-config/skills/workflow/finishing-a-development-branch/SKILL.md +0 -117
- package/ai-config/skills/workflow/git-github/SKILL.md +0 -334
- package/ai-config/skills/workflow/git-github/references/examples.md +0 -160
- package/ai-config/skills/workflow/git-workflow/SKILL.md +0 -214
- package/ai-config/skills/workflow/ide-plugins/SKILL.md +0 -277
- package/ai-config/skills/workflow/ide-plugins-intellij/SKILL.md +0 -401
- package/ai-config/skills/workflow/obsidian-brain-workflow/SKILL.md +0 -199
- package/ai-config/skills/workflow/using-git-worktrees/SKILL.md +0 -100
- package/ai-config/skills/workflow/verification-before-completion/SKILL.md +0 -73
- package/ai-config/skills/workflow/wave-workflow/SKILL.md +0 -178
- package/schemas/agent.schema.json +0 -34
- package/schemas/ai-config.schema.json +0 -28
- package/schemas/plugin.schema.json +0 -62
- package/schemas/skill.schema.json +0 -44
|
@@ -1,396 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: chi-router
|
|
3
|
-
description: >
|
|
4
|
-
Go HTTP router patterns with Chi v5 - routing, middleware, handlers.
|
|
5
|
-
Trigger: chi, go router, http handlers, go middleware, rest api go
|
|
6
|
-
tools:
|
|
7
|
-
- Read
|
|
8
|
-
- Write
|
|
9
|
-
- Bash
|
|
10
|
-
- Grep
|
|
11
|
-
metadata:
|
|
12
|
-
author: plataforma-industrial
|
|
13
|
-
version: "2.0"
|
|
14
|
-
tags: [go, chi, http, router, backend]
|
|
15
|
-
updated: "2026-02"
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
# Chi Router Skill
|
|
19
|
-
|
|
20
|
-
Go HTTP router patterns using Chi v5 for REST APIs.
|
|
21
|
-
|
|
22
|
-
## Stack
|
|
23
|
-
|
|
24
|
-
```go
|
|
25
|
-
require (
|
|
26
|
-
github.com/go-chi/chi/v5 v5.0.12
|
|
27
|
-
github.com/go-chi/cors v1.2.1
|
|
28
|
-
github.com/go-chi/httprate v0.9.0
|
|
29
|
-
github.com/go-chi/render v1.0.3
|
|
30
|
-
)
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Project Structure
|
|
34
|
-
|
|
35
|
-
```
|
|
36
|
-
apps/api/
|
|
37
|
-
├── cmd/api/main.go
|
|
38
|
-
├── internal/
|
|
39
|
-
│ ├── config/config.go
|
|
40
|
-
│ ├── handler/
|
|
41
|
-
│ │ ├── handler.go
|
|
42
|
-
│ │ ├── resource_handler.go
|
|
43
|
-
│ │ └── response.go
|
|
44
|
-
│ ├── middleware/
|
|
45
|
-
│ │ ├── auth.go
|
|
46
|
-
│ │ ├── tenant.go
|
|
47
|
-
│ │ └── logging.go
|
|
48
|
-
│ ├── router/router.go
|
|
49
|
-
│ ├── service/
|
|
50
|
-
│ └── repository/
|
|
51
|
-
└── go.mod
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Router Setup
|
|
55
|
-
|
|
56
|
-
```go
|
|
57
|
-
package router
|
|
58
|
-
|
|
59
|
-
import (
|
|
60
|
-
"time"
|
|
61
|
-
"github.com/go-chi/chi/v5"
|
|
62
|
-
"github.com/go-chi/chi/v5/middleware"
|
|
63
|
-
"github.com/go-chi/cors"
|
|
64
|
-
"github.com/go-chi/httprate"
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
func New(handlers *Handlers, cfg *config.Config) *chi.Mux {
|
|
68
|
-
r := chi.NewRouter()
|
|
69
|
-
|
|
70
|
-
// Global middleware
|
|
71
|
-
r.Use(middleware.RequestID)
|
|
72
|
-
r.Use(middleware.RealIP)
|
|
73
|
-
r.Use(middleware.Logger)
|
|
74
|
-
r.Use(middleware.Recoverer)
|
|
75
|
-
r.Use(middleware.Timeout(30 * time.Second))
|
|
76
|
-
|
|
77
|
-
// CORS
|
|
78
|
-
r.Use(cors.Handler(cors.Options{
|
|
79
|
-
AllowedOrigins: cfg.CORSOrigins,
|
|
80
|
-
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
|
81
|
-
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"},
|
|
82
|
-
AllowCredentials: true,
|
|
83
|
-
MaxAge: 300,
|
|
84
|
-
}))
|
|
85
|
-
|
|
86
|
-
// Rate limiting
|
|
87
|
-
r.Use(httprate.LimitByIP(100, time.Minute))
|
|
88
|
-
|
|
89
|
-
// Health (no auth)
|
|
90
|
-
r.Get("/health", handlers.Health.Live)
|
|
91
|
-
|
|
92
|
-
// API v1
|
|
93
|
-
r.Route("/api/v1", func(r chi.Router) {
|
|
94
|
-
// Public
|
|
95
|
-
r.Post("/auth/login", handlers.Auth.Login)
|
|
96
|
-
|
|
97
|
-
// Protected
|
|
98
|
-
r.Group(func(r chi.Router) {
|
|
99
|
-
r.Use(JWTAuthMiddleware(cfg.JWTSecret))
|
|
100
|
-
r.Use(TenantMiddleware)
|
|
101
|
-
|
|
102
|
-
r.Route("/resources", func(r chi.Router) {
|
|
103
|
-
r.Get("/", handlers.Resource.List)
|
|
104
|
-
r.Post("/", handlers.Resource.Create)
|
|
105
|
-
r.Route("/{resourceID}", func(r chi.Router) {
|
|
106
|
-
r.Use(ResourceCtx)
|
|
107
|
-
r.Get("/", handlers.Resource.Get)
|
|
108
|
-
r.Put("/", handlers.Resource.Update)
|
|
109
|
-
r.Delete("/", handlers.Resource.Delete)
|
|
110
|
-
})
|
|
111
|
-
})
|
|
112
|
-
})
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
return r
|
|
116
|
-
}
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
## Handler Pattern
|
|
120
|
-
|
|
121
|
-
```go
|
|
122
|
-
package handler
|
|
123
|
-
|
|
124
|
-
import (
|
|
125
|
-
"encoding/json"
|
|
126
|
-
"net/http"
|
|
127
|
-
"github.com/go-chi/chi/v5"
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
type ResourceHandler struct {
|
|
131
|
-
service *service.ResourceService
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
func NewResourceHandler(svc *service.ResourceService) *ResourceHandler {
|
|
135
|
-
return &ResourceHandler{service: svc}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
func (h *ResourceHandler) List(w http.ResponseWriter, r *http.Request) {
|
|
139
|
-
ctx := r.Context()
|
|
140
|
-
tenantID := TenantFromContext(ctx)
|
|
141
|
-
|
|
142
|
-
filters := &Filters{
|
|
143
|
-
Status: r.URL.Query().Get("status"),
|
|
144
|
-
Page: parseIntOrDefault(r.URL.Query().Get("page"), 1),
|
|
145
|
-
Limit: parseIntOrDefault(r.URL.Query().Get("limit"), 20),
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
items, total, err := h.service.List(ctx, tenantID, filters)
|
|
149
|
-
if err != nil {
|
|
150
|
-
respondError(w, http.StatusInternalServerError, "failed to list")
|
|
151
|
-
return
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
respondPaginated(w, items, total, filters.Page, filters.Limit)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
func (h *ResourceHandler) Create(w http.ResponseWriter, r *http.Request) {
|
|
158
|
-
ctx := r.Context()
|
|
159
|
-
tenantID := TenantFromContext(ctx)
|
|
160
|
-
|
|
161
|
-
var input CreateInput
|
|
162
|
-
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
|
|
163
|
-
respondError(w, http.StatusBadRequest, "invalid json")
|
|
164
|
-
return
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if err := input.Validate(); err != nil {
|
|
168
|
-
respondValidationError(w, err)
|
|
169
|
-
return
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
item, err := h.service.Create(ctx, tenantID, &input)
|
|
173
|
-
if err != nil {
|
|
174
|
-
respondError(w, http.StatusInternalServerError, "failed to create")
|
|
175
|
-
return
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
respondJSON(w, http.StatusCreated, DataResponse{Data: item})
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
func (h *ResourceHandler) Get(w http.ResponseWriter, r *http.Request) {
|
|
182
|
-
resource := ResourceFromContext(r.Context())
|
|
183
|
-
respondJSON(w, http.StatusOK, DataResponse{Data: resource})
|
|
184
|
-
}
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
## JWT Middleware
|
|
188
|
-
|
|
189
|
-
```go
|
|
190
|
-
package middleware
|
|
191
|
-
|
|
192
|
-
import (
|
|
193
|
-
"context"
|
|
194
|
-
"net/http"
|
|
195
|
-
"strings"
|
|
196
|
-
"github.com/golang-jwt/jwt/v5"
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
type contextKey string
|
|
200
|
-
|
|
201
|
-
const (
|
|
202
|
-
ClaimsKey contextKey = "claims"
|
|
203
|
-
UserIDKey contextKey = "userID"
|
|
204
|
-
TenantKey contextKey = "tenantID"
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
func JWTAuthMiddleware(secret string) func(next http.Handler) http.Handler {
|
|
208
|
-
return func(next http.Handler) http.Handler {
|
|
209
|
-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
210
|
-
authHeader := r.Header.Get("Authorization")
|
|
211
|
-
if authHeader == "" {
|
|
212
|
-
respondError(w, http.StatusUnauthorized, "missing authorization")
|
|
213
|
-
return
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
parts := strings.Split(authHeader, " ")
|
|
217
|
-
if len(parts) != 2 || parts[0] != "Bearer" {
|
|
218
|
-
respondError(w, http.StatusUnauthorized, "invalid authorization")
|
|
219
|
-
return
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
token, err := jwt.Parse(parts[1], func(token *jwt.Token) (interface{}, error) {
|
|
223
|
-
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
|
224
|
-
return nil, fmt.Errorf("unexpected signing method")
|
|
225
|
-
}
|
|
226
|
-
return []byte(secret), nil
|
|
227
|
-
})
|
|
228
|
-
|
|
229
|
-
if err != nil || !token.Valid {
|
|
230
|
-
respondError(w, http.StatusUnauthorized, "invalid token")
|
|
231
|
-
return
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
claims := token.Claims.(jwt.MapClaims)
|
|
235
|
-
ctx := context.WithValue(r.Context(), ClaimsKey, claims)
|
|
236
|
-
ctx = context.WithValue(ctx, UserIDKey, claims["sub"].(string))
|
|
237
|
-
|
|
238
|
-
next.ServeHTTP(w, r.WithContext(ctx))
|
|
239
|
-
})
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
func UserIDFromContext(ctx context.Context) string {
|
|
244
|
-
if v := ctx.Value(UserIDKey); v != nil {
|
|
245
|
-
return v.(string)
|
|
246
|
-
}
|
|
247
|
-
return ""
|
|
248
|
-
}
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
## Resource Context Middleware
|
|
252
|
-
|
|
253
|
-
```go
|
|
254
|
-
func ResourceCtx(next http.Handler) http.Handler {
|
|
255
|
-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
256
|
-
resourceID := chi.URLParam(r, "resourceID")
|
|
257
|
-
|
|
258
|
-
id, err := uuid.Parse(resourceID)
|
|
259
|
-
if err != nil {
|
|
260
|
-
respondError(w, http.StatusBadRequest, "invalid id")
|
|
261
|
-
return
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
tenantID := TenantFromContext(r.Context())
|
|
265
|
-
resource, err := resourceService.GetByID(r.Context(), tenantID, id)
|
|
266
|
-
if err != nil {
|
|
267
|
-
respondError(w, http.StatusNotFound, "not found")
|
|
268
|
-
return
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
ctx := context.WithValue(r.Context(), resourceContextKey{}, resource)
|
|
272
|
-
next.ServeHTTP(w, r.WithContext(ctx))
|
|
273
|
-
})
|
|
274
|
-
}
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
## Response Helpers
|
|
278
|
-
|
|
279
|
-
```go
|
|
280
|
-
package handler
|
|
281
|
-
|
|
282
|
-
type DataResponse struct {
|
|
283
|
-
Data interface{} `json:"data"`
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
type ErrorResponse struct {
|
|
287
|
-
Error string `json:"error"`
|
|
288
|
-
Code string `json:"code,omitempty"`
|
|
289
|
-
Details map[string]string `json:"details,omitempty"`
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
type PaginatedResponse struct {
|
|
293
|
-
Data interface{} `json:"data"`
|
|
294
|
-
Meta Meta `json:"meta"`
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
type Meta struct {
|
|
298
|
-
Total int `json:"total"`
|
|
299
|
-
Page int `json:"page"`
|
|
300
|
-
Limit int `json:"limit"`
|
|
301
|
-
TotalPages int `json:"totalPages"`
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
func respondJSON(w http.ResponseWriter, status int, data interface{}) {
|
|
305
|
-
w.Header().Set("Content-Type", "application/json")
|
|
306
|
-
w.WriteHeader(status)
|
|
307
|
-
json.NewEncoder(w).Encode(data)
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
func respondError(w http.ResponseWriter, status int, message string) {
|
|
311
|
-
respondJSON(w, status, ErrorResponse{Error: message})
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
func respondPaginated(w http.ResponseWriter, data interface{}, total, page, limit int) {
|
|
315
|
-
totalPages := (total + limit - 1) / limit
|
|
316
|
-
respondJSON(w, http.StatusOK, PaginatedResponse{
|
|
317
|
-
Data: data,
|
|
318
|
-
Meta: Meta{Total: total, Page: page, Limit: limit, TotalPages: totalPages},
|
|
319
|
-
})
|
|
320
|
-
}
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
## Route Patterns
|
|
324
|
-
|
|
325
|
-
```go
|
|
326
|
-
// Middleware per group
|
|
327
|
-
r.Group(func(r chi.Router) {
|
|
328
|
-
r.Use(AuthMiddleware)
|
|
329
|
-
r.Get("/protected", handler)
|
|
330
|
-
})
|
|
331
|
-
|
|
332
|
-
// Middleware per route
|
|
333
|
-
r.With(RateLimitMiddleware).Post("/login", handler)
|
|
334
|
-
|
|
335
|
-
// Mount subrouters
|
|
336
|
-
r.Mount("/api/v1/resources", ResourceRouter(handler))
|
|
337
|
-
|
|
338
|
-
// Get URL param
|
|
339
|
-
id := chi.URLParam(r, "id")
|
|
340
|
-
|
|
341
|
-
// Get query param
|
|
342
|
-
status := r.URL.Query().Get("status")
|
|
343
|
-
|
|
344
|
-
// Route context
|
|
345
|
-
rctx := chi.RouteContext(r.Context())
|
|
346
|
-
pattern := rctx.RoutePattern()
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
## Testing
|
|
350
|
-
|
|
351
|
-
```go
|
|
352
|
-
func TestHandler_List(t *testing.T) {
|
|
353
|
-
mockService := &MockService{}
|
|
354
|
-
handler := NewHandler(mockService)
|
|
355
|
-
|
|
356
|
-
mockService.On("List", mock.Anything, mock.Anything).Return(
|
|
357
|
-
[]*model.Item{{ID: "1", Name: "Test"}}, 1, nil,
|
|
358
|
-
)
|
|
359
|
-
|
|
360
|
-
req := httptest.NewRequest("GET", "/api/v1/resources", nil)
|
|
361
|
-
req = req.WithContext(context.WithValue(req.Context(), TenantKey, "tenant-1"))
|
|
362
|
-
rec := httptest.NewRecorder()
|
|
363
|
-
|
|
364
|
-
handler.List(rec, req)
|
|
365
|
-
|
|
366
|
-
assert.Equal(t, http.StatusOK, rec.Code)
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
func TestRoutes(t *testing.T) {
|
|
370
|
-
r := chi.NewRouter()
|
|
371
|
-
r.Use(testAuthMiddleware)
|
|
372
|
-
r.Route("/api/v1/resources", func(r chi.Router) {
|
|
373
|
-
r.Get("/", handler.List)
|
|
374
|
-
r.Get("/{id}", handler.Get)
|
|
375
|
-
})
|
|
376
|
-
|
|
377
|
-
req := httptest.NewRequest("GET", "/api/v1/resources", nil)
|
|
378
|
-
rec := httptest.NewRecorder()
|
|
379
|
-
r.ServeHTTP(rec, req)
|
|
380
|
-
assert.Equal(t, http.StatusOK, rec.Code)
|
|
381
|
-
}
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
## Best Practices
|
|
385
|
-
|
|
386
|
-
1. **Middleware levels**: Global for logging, group for auth, route for rate limiting
|
|
387
|
-
2. **Resource context pattern**: Load and validate resources in middleware
|
|
388
|
-
3. **Consistent responses**: Always use helper functions
|
|
389
|
-
4. **Validate input**: Decode JSON, then validate before processing
|
|
390
|
-
|
|
391
|
-
## Related Skills
|
|
392
|
-
|
|
393
|
-
- `pgx-postgres`: Database access patterns
|
|
394
|
-
- `jwt-auth`: Auth middleware integration
|
|
395
|
-
- `redis-cache`: Response caching
|
|
396
|
-
- `opentelemetry`: Distributed tracing
|
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: error-handling
|
|
3
|
-
description: >
|
|
4
|
-
API error handling patterns. RFC 7807 Problem Details, error codes, client handling.
|
|
5
|
-
Trigger: error handling, exceptions, problem details, RFC 7807, error response
|
|
6
|
-
tools:
|
|
7
|
-
- Read
|
|
8
|
-
- Write
|
|
9
|
-
- Edit
|
|
10
|
-
- Grep
|
|
11
|
-
metadata:
|
|
12
|
-
author: apigen-team
|
|
13
|
-
version: "1.0"
|
|
14
|
-
tags: [errors, exceptions, api-design, standards]
|
|
15
|
-
scope: ["**/exceptions/**", "**/error/**"]
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
# Error Handling Concepts
|
|
19
|
-
|
|
20
|
-
## RFC 7807 Problem Details
|
|
21
|
-
|
|
22
|
-
```json
|
|
23
|
-
{
|
|
24
|
-
"type": "https://api.example.com/errors/validation",
|
|
25
|
-
"title": "Validation Error",
|
|
26
|
-
"status": 400,
|
|
27
|
-
"detail": "The request contains invalid data",
|
|
28
|
-
"instance": "/api/users/123",
|
|
29
|
-
"errors": [
|
|
30
|
-
{
|
|
31
|
-
"field": "email",
|
|
32
|
-
"message": "Invalid email format"
|
|
33
|
-
}
|
|
34
|
-
]
|
|
35
|
-
}
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### Required Fields
|
|
39
|
-
```
|
|
40
|
-
type: URI identifying the error type (documentation link)
|
|
41
|
-
title: Human-readable summary (stable, not localized)
|
|
42
|
-
status: HTTP status code
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### Optional Fields
|
|
46
|
-
```
|
|
47
|
-
detail: Human-readable explanation (may vary per occurrence)
|
|
48
|
-
instance: URI identifying the specific occurrence
|
|
49
|
-
<extensions>: Custom fields for additional context
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## Error Categorization
|
|
53
|
-
|
|
54
|
-
### Client Errors (4xx)
|
|
55
|
-
```
|
|
56
|
-
400 Bad Request
|
|
57
|
-
- Malformed syntax
|
|
58
|
-
- Invalid field values
|
|
59
|
-
- Missing required fields
|
|
60
|
-
|
|
61
|
-
401 Unauthorized
|
|
62
|
-
- Missing authentication
|
|
63
|
-
- Expired token
|
|
64
|
-
- Invalid credentials
|
|
65
|
-
|
|
66
|
-
403 Forbidden
|
|
67
|
-
- Valid auth but insufficient permissions
|
|
68
|
-
- Resource access denied
|
|
69
|
-
- Rate limit exceeded (also 429)
|
|
70
|
-
|
|
71
|
-
404 Not Found
|
|
72
|
-
- Resource doesn't exist
|
|
73
|
-
- Endpoint not found
|
|
74
|
-
- Soft-deleted resource
|
|
75
|
-
|
|
76
|
-
409 Conflict
|
|
77
|
-
- Duplicate resource
|
|
78
|
-
- Optimistic locking failure
|
|
79
|
-
- State conflict
|
|
80
|
-
|
|
81
|
-
422 Unprocessable Entity
|
|
82
|
-
- Semantic errors
|
|
83
|
-
- Business rule violations
|
|
84
|
-
- Validation errors (alternative to 400)
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Server Errors (5xx)
|
|
88
|
-
```
|
|
89
|
-
500 Internal Server Error
|
|
90
|
-
- Unexpected exceptions
|
|
91
|
-
- Programming errors
|
|
92
|
-
- Configuration issues
|
|
93
|
-
|
|
94
|
-
502 Bad Gateway
|
|
95
|
-
- Upstream service failure
|
|
96
|
-
- Invalid upstream response
|
|
97
|
-
|
|
98
|
-
503 Service Unavailable
|
|
99
|
-
- Maintenance mode
|
|
100
|
-
- Overloaded
|
|
101
|
-
- Circuit breaker open
|
|
102
|
-
|
|
103
|
-
504 Gateway Timeout
|
|
104
|
-
- Upstream timeout
|
|
105
|
-
- Slow dependency
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
## Error Code System
|
|
109
|
-
|
|
110
|
-
```
|
|
111
|
-
Format: DOMAIN-CATEGORY-NUMBER
|
|
112
|
-
|
|
113
|
-
Examples:
|
|
114
|
-
AUTH-TOKEN-001: Token expired
|
|
115
|
-
AUTH-TOKEN-002: Token invalid
|
|
116
|
-
AUTH-TOKEN-003: Token revoked
|
|
117
|
-
|
|
118
|
-
USER-VAL-001: Email already exists
|
|
119
|
-
USER-VAL-002: Password too weak
|
|
120
|
-
USER-VAL-003: Invalid phone format
|
|
121
|
-
|
|
122
|
-
ORDER-BIZ-001: Insufficient inventory
|
|
123
|
-
ORDER-BIZ-002: Shipping address invalid
|
|
124
|
-
ORDER-BIZ-003: Payment declined
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
## Error Response Patterns
|
|
128
|
-
|
|
129
|
-
### Validation Errors
|
|
130
|
-
```json
|
|
131
|
-
{
|
|
132
|
-
"type": "https://api.example.com/errors/validation",
|
|
133
|
-
"title": "Validation Failed",
|
|
134
|
-
"status": 400,
|
|
135
|
-
"detail": "2 validation errors occurred",
|
|
136
|
-
"errors": [
|
|
137
|
-
{
|
|
138
|
-
"field": "email",
|
|
139
|
-
"code": "USER-VAL-001",
|
|
140
|
-
"message": "Email is already registered",
|
|
141
|
-
"rejectedValue": "test@example.com"
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
"field": "password",
|
|
145
|
-
"code": "USER-VAL-002",
|
|
146
|
-
"message": "Password must be at least 8 characters",
|
|
147
|
-
"rejectedValue": null
|
|
148
|
-
}
|
|
149
|
-
]
|
|
150
|
-
}
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
### Business Logic Errors
|
|
154
|
-
```json
|
|
155
|
-
{
|
|
156
|
-
"type": "https://api.example.com/errors/business",
|
|
157
|
-
"title": "Business Rule Violation",
|
|
158
|
-
"status": 422,
|
|
159
|
-
"detail": "Cannot complete order",
|
|
160
|
-
"code": "ORDER-BIZ-001",
|
|
161
|
-
"context": {
|
|
162
|
-
"requiredStock": 10,
|
|
163
|
-
"availableStock": 3,
|
|
164
|
-
"productId": "PROD-123"
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
### Authentication Errors
|
|
170
|
-
```json
|
|
171
|
-
{
|
|
172
|
-
"type": "https://api.example.com/errors/authentication",
|
|
173
|
-
"title": "Authentication Failed",
|
|
174
|
-
"status": 401,
|
|
175
|
-
"detail": "Token has expired",
|
|
176
|
-
"code": "AUTH-TOKEN-001",
|
|
177
|
-
"expiredAt": "2024-01-15T10:30:00Z",
|
|
178
|
-
"refreshable": true
|
|
179
|
-
}
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
## Client Error Handling
|
|
183
|
-
|
|
184
|
-
### Retry Strategy
|
|
185
|
-
```
|
|
186
|
-
Retryable errors:
|
|
187
|
-
- 429 Too Many Requests (with backoff)
|
|
188
|
-
- 500 Internal Server Error (limited)
|
|
189
|
-
- 502 Bad Gateway
|
|
190
|
-
- 503 Service Unavailable
|
|
191
|
-
- 504 Gateway Timeout
|
|
192
|
-
|
|
193
|
-
Non-retryable:
|
|
194
|
-
- 400 Bad Request
|
|
195
|
-
- 401 Unauthorized
|
|
196
|
-
- 403 Forbidden
|
|
197
|
-
- 404 Not Found
|
|
198
|
-
- 409 Conflict
|
|
199
|
-
- 422 Unprocessable Entity
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
### Exponential Backoff
|
|
203
|
-
```
|
|
204
|
-
Attempt 1: Wait 1 second
|
|
205
|
-
Attempt 2: Wait 2 seconds
|
|
206
|
-
Attempt 3: Wait 4 seconds
|
|
207
|
-
Attempt 4: Wait 8 seconds
|
|
208
|
-
...with jitter to prevent thundering herd
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
## Logging Strategy
|
|
212
|
-
|
|
213
|
-
```
|
|
214
|
-
Log levels by error type:
|
|
215
|
-
|
|
216
|
-
ERROR (investigate immediately):
|
|
217
|
-
- 500 Internal Server Error
|
|
218
|
-
- Unhandled exceptions
|
|
219
|
-
- Data corruption
|
|
220
|
-
|
|
221
|
-
WARN (monitor trends):
|
|
222
|
-
- 401/403 authentication failures
|
|
223
|
-
- 409 conflicts
|
|
224
|
-
- Circuit breaker trips
|
|
225
|
-
|
|
226
|
-
INFO (normal operations):
|
|
227
|
-
- 400/422 validation errors
|
|
228
|
-
- 404 not found
|
|
229
|
-
- Rate limiting
|
|
230
|
-
|
|
231
|
-
DEBUG (troubleshooting):
|
|
232
|
-
- Full request/response
|
|
233
|
-
- Stack traces
|
|
234
|
-
- Context details
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
## Security Considerations
|
|
238
|
-
|
|
239
|
-
```
|
|
240
|
-
Never expose:
|
|
241
|
-
- Stack traces to clients
|
|
242
|
-
- Internal paths or class names
|
|
243
|
-
- Database error details
|
|
244
|
-
- System configuration
|
|
245
|
-
|
|
246
|
-
Always:
|
|
247
|
-
- Use generic messages for auth errors
|
|
248
|
-
- Log full details server-side
|
|
249
|
-
- Correlate with request ID
|
|
250
|
-
- Rate limit error responses
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
## Related Skills
|
|
254
|
-
|
|
255
|
-
- `exceptions-spring`: Spring Boot exception handling
|