javi-forge 1.1.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 +38 -10
- package/ci-local/hooks/pre-commit +10 -155
- package/ci-local/hooks/pre-push +12 -29
- package/dist/commands/ci.d.ts +33 -0
- package/dist/commands/ci.js +341 -0
- package/dist/commands/init.js +5 -0
- package/dist/index.js +39 -5
- package/dist/lib/docker.d.ts +43 -0
- package/dist/lib/docker.js +223 -0
- package/dist/ui/CI.d.ts +9 -0
- package/dist/ui/CI.js +91 -0
- package/package.json +9 -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,512 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: pgx-postgres
|
|
3
|
-
description: >
|
|
4
|
-
PostgreSQL driver for Go with pgx v5 - connection pools, queries, transactions.
|
|
5
|
-
Trigger: pgx, postgres go, postgresql driver, go database, sql go
|
|
6
|
-
tools:
|
|
7
|
-
- Read
|
|
8
|
-
- Write
|
|
9
|
-
- Bash
|
|
10
|
-
- Grep
|
|
11
|
-
metadata:
|
|
12
|
-
author: plataforma-industrial
|
|
13
|
-
version: "2.0"
|
|
14
|
-
tags: [go, postgresql, pgx, database, backend]
|
|
15
|
-
updated: "2026-02"
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
# PGX PostgreSQL Skill
|
|
19
|
-
|
|
20
|
-
PostgreSQL database patterns using pgx v5 for Go applications.
|
|
21
|
-
|
|
22
|
-
## Stack
|
|
23
|
-
|
|
24
|
-
```go
|
|
25
|
-
require (
|
|
26
|
-
github.com/jackc/pgx/v5 v5.5.5
|
|
27
|
-
github.com/jackc/pgx/v5/pgxpool v5.5.5
|
|
28
|
-
github.com/jackc/pgx/v5/stdlib v5.5.5
|
|
29
|
-
)
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Connection Pool
|
|
33
|
-
|
|
34
|
-
```go
|
|
35
|
-
package database
|
|
36
|
-
|
|
37
|
-
import (
|
|
38
|
-
"context"
|
|
39
|
-
"fmt"
|
|
40
|
-
"time"
|
|
41
|
-
"github.com/jackc/pgx/v5/pgxpool"
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
type PostgresDB struct {
|
|
45
|
-
pool *pgxpool.Pool
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
func NewPostgres(ctx context.Context, connString string) (*PostgresDB, error) {
|
|
49
|
-
config, err := pgxpool.ParseConfig(connString)
|
|
50
|
-
if err != nil {
|
|
51
|
-
return nil, fmt.Errorf("parse config: %w", err)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Pool configuration
|
|
55
|
-
config.MaxConns = 25
|
|
56
|
-
config.MinConns = 5
|
|
57
|
-
config.MaxConnLifetime = time.Hour
|
|
58
|
-
config.MaxConnIdleTime = 30 * time.Minute
|
|
59
|
-
config.HealthCheckPeriod = time.Minute
|
|
60
|
-
|
|
61
|
-
pool, err := pgxpool.NewWithConfig(ctx, config)
|
|
62
|
-
if err != nil {
|
|
63
|
-
return nil, fmt.Errorf("create pool: %w", err)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if err := pool.Ping(ctx); err != nil {
|
|
67
|
-
return nil, fmt.Errorf("ping: %w", err)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return &PostgresDB{pool: pool}, nil
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
func (db *PostgresDB) Close() { db.pool.Close() }
|
|
74
|
-
func (db *PostgresDB) Pool() *pgxpool.Pool { return db.pool }
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## Database Config
|
|
78
|
-
|
|
79
|
-
```go
|
|
80
|
-
type DatabaseConfig struct {
|
|
81
|
-
Host string
|
|
82
|
-
Port int
|
|
83
|
-
User string
|
|
84
|
-
Password string
|
|
85
|
-
Database string
|
|
86
|
-
SSLMode string
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
func (c *DatabaseConfig) ConnectionString() string {
|
|
90
|
-
return fmt.Sprintf(
|
|
91
|
-
"postgres://%s:%s@%s:%d/%s?sslmode=%s",
|
|
92
|
-
c.User, c.Password, c.Host, c.Port, c.Database, c.SSLMode,
|
|
93
|
-
)
|
|
94
|
-
}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
## Repository Pattern
|
|
98
|
-
|
|
99
|
-
### Base Repository
|
|
100
|
-
|
|
101
|
-
```go
|
|
102
|
-
package repository
|
|
103
|
-
|
|
104
|
-
import (
|
|
105
|
-
"context"
|
|
106
|
-
"github.com/jackc/pgx/v5"
|
|
107
|
-
"github.com/jackc/pgx/v5/pgxpool"
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
type BaseRepository struct {
|
|
111
|
-
pool *pgxpool.Pool
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
func NewBaseRepository(pool *pgxpool.Pool) *BaseRepository {
|
|
115
|
-
return &BaseRepository{pool: pool}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
func (r *BaseRepository) WithTx(ctx context.Context, fn func(tx pgx.Tx) error) error {
|
|
119
|
-
tx, err := r.pool.Begin(ctx)
|
|
120
|
-
if err != nil {
|
|
121
|
-
return err
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
defer func() {
|
|
125
|
-
if p := recover(); p != nil {
|
|
126
|
-
tx.Rollback(ctx)
|
|
127
|
-
panic(p)
|
|
128
|
-
}
|
|
129
|
-
}()
|
|
130
|
-
|
|
131
|
-
if err := fn(tx); err != nil {
|
|
132
|
-
tx.Rollback(ctx)
|
|
133
|
-
return err
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return tx.Commit(ctx)
|
|
137
|
-
}
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### Entity Repository
|
|
141
|
-
|
|
142
|
-
```go
|
|
143
|
-
type EntityRepository struct {
|
|
144
|
-
*BaseRepository
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
func NewEntityRepository(pool *pgxpool.Pool) *EntityRepository {
|
|
148
|
-
return &EntityRepository{BaseRepository: NewBaseRepository(pool)}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
func (r *EntityRepository) GetByID(ctx context.Context, id uuid.UUID) (*model.Entity, error) {
|
|
152
|
-
query := `SELECT id, name, status, tenant_id, created_at, updated_at
|
|
153
|
-
FROM entities WHERE id = $1`
|
|
154
|
-
|
|
155
|
-
var e model.Entity
|
|
156
|
-
err := r.pool.QueryRow(ctx, query, id).Scan(
|
|
157
|
-
&e.ID, &e.Name, &e.Status, &e.TenantID, &e.CreatedAt, &e.UpdatedAt,
|
|
158
|
-
)
|
|
159
|
-
if err != nil {
|
|
160
|
-
if err == pgx.ErrNoRows {
|
|
161
|
-
return nil, ErrNotFound
|
|
162
|
-
}
|
|
163
|
-
return nil, err
|
|
164
|
-
}
|
|
165
|
-
return &e, nil
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
func (r *EntityRepository) List(ctx context.Context, filters *Filters) ([]*model.Entity, int, error) {
|
|
169
|
-
// Count
|
|
170
|
-
countQuery := `SELECT COUNT(*) FROM entities WHERE tenant_id = $1`
|
|
171
|
-
args := []interface{}{filters.TenantID}
|
|
172
|
-
|
|
173
|
-
if filters.Status != "" {
|
|
174
|
-
countQuery += ` AND status = $2`
|
|
175
|
-
args = append(args, filters.Status)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
var total int
|
|
179
|
-
if err := r.pool.QueryRow(ctx, countQuery, args...).Scan(&total); err != nil {
|
|
180
|
-
return nil, 0, err
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Data
|
|
184
|
-
query := `SELECT id, name, status, tenant_id, created_at, updated_at
|
|
185
|
-
FROM entities WHERE tenant_id = $1`
|
|
186
|
-
|
|
187
|
-
if filters.Status != "" {
|
|
188
|
-
query += ` AND status = $2`
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
query += fmt.Sprintf(` ORDER BY name LIMIT $%d OFFSET $%d`, len(args)+1, len(args)+2)
|
|
192
|
-
args = append(args, filters.Limit, (filters.Page-1)*filters.Limit)
|
|
193
|
-
|
|
194
|
-
rows, err := r.pool.Query(ctx, query, args...)
|
|
195
|
-
if err != nil {
|
|
196
|
-
return nil, 0, err
|
|
197
|
-
}
|
|
198
|
-
defer rows.Close()
|
|
199
|
-
|
|
200
|
-
entities := make([]*model.Entity, 0)
|
|
201
|
-
for rows.Next() {
|
|
202
|
-
var e model.Entity
|
|
203
|
-
if err := rows.Scan(&e.ID, &e.Name, &e.Status, &e.TenantID, &e.CreatedAt, &e.UpdatedAt); err != nil {
|
|
204
|
-
return nil, 0, err
|
|
205
|
-
}
|
|
206
|
-
entities = append(entities, &e)
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return entities, total, rows.Err()
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
func (r *EntityRepository) Create(ctx context.Context, e *model.Entity) error {
|
|
213
|
-
query := `INSERT INTO entities (id, name, status, tenant_id, created_at, updated_at)
|
|
214
|
-
VALUES ($1, $2, $3, $4, $5, $6)`
|
|
215
|
-
|
|
216
|
-
_, err := r.pool.Exec(ctx, query, e.ID, e.Name, e.Status, e.TenantID, e.CreatedAt, e.UpdatedAt)
|
|
217
|
-
return err
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
func (r *EntityRepository) Update(ctx context.Context, e *model.Entity) error {
|
|
221
|
-
query := `UPDATE entities SET name = $2, status = $3, updated_at = $4 WHERE id = $1`
|
|
222
|
-
|
|
223
|
-
result, err := r.pool.Exec(ctx, query, e.ID, e.Name, e.Status, time.Now())
|
|
224
|
-
if err != nil {
|
|
225
|
-
return err
|
|
226
|
-
}
|
|
227
|
-
if result.RowsAffected() == 0 {
|
|
228
|
-
return ErrNotFound
|
|
229
|
-
}
|
|
230
|
-
return nil
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
func (r *EntityRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
|
234
|
-
result, err := r.pool.Exec(ctx, `DELETE FROM entities WHERE id = $1`, id)
|
|
235
|
-
if err != nil {
|
|
236
|
-
return err
|
|
237
|
-
}
|
|
238
|
-
if result.RowsAffected() == 0 {
|
|
239
|
-
return ErrNotFound
|
|
240
|
-
}
|
|
241
|
-
return nil
|
|
242
|
-
}
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
## Batch Operations
|
|
246
|
-
|
|
247
|
-
```go
|
|
248
|
-
// Batch Insert
|
|
249
|
-
func (r *EntityRepository) BatchCreate(ctx context.Context, entities []*model.Entity) error {
|
|
250
|
-
batch := &pgx.Batch{}
|
|
251
|
-
|
|
252
|
-
for _, e := range entities {
|
|
253
|
-
batch.Queue(`INSERT INTO entities (id, name, status, tenant_id, created_at)
|
|
254
|
-
VALUES ($1, $2, $3, $4, $5)`,
|
|
255
|
-
e.ID, e.Name, e.Status, e.TenantID, e.CreatedAt)
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
results := r.pool.SendBatch(ctx, batch)
|
|
259
|
-
defer results.Close()
|
|
260
|
-
|
|
261
|
-
for range entities {
|
|
262
|
-
if _, err := results.Exec(); err != nil {
|
|
263
|
-
return err
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
return nil
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Bulk Insert with CopyFrom
|
|
270
|
-
func (r *EntityRepository) BulkInsert(ctx context.Context, entities []*model.Entity) error {
|
|
271
|
-
columns := []string{"id", "name", "status", "tenant_id", "created_at"}
|
|
272
|
-
|
|
273
|
-
rows := make([][]interface{}, len(entities))
|
|
274
|
-
for i, e := range entities {
|
|
275
|
-
rows[i] = []interface{}{e.ID, e.Name, e.Status, e.TenantID, e.CreatedAt}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
_, err := r.pool.CopyFrom(ctx, pgx.Identifier{"entities"}, columns, pgx.CopyFromRows(rows))
|
|
279
|
-
return err
|
|
280
|
-
}
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
## Transactions
|
|
284
|
-
|
|
285
|
-
```go
|
|
286
|
-
// Simple transaction
|
|
287
|
-
func (r *EntityRepository) CreateWithRelated(ctx context.Context, entity *model.Entity, related []*model.Related) error {
|
|
288
|
-
return r.WithTx(ctx, func(tx pgx.Tx) error {
|
|
289
|
-
if _, err := tx.Exec(ctx, `INSERT INTO entities (...) VALUES (...)`, ...); err != nil {
|
|
290
|
-
return err
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
for _, rel := range related {
|
|
294
|
-
if _, err := tx.Exec(ctx, `INSERT INTO related (...) VALUES (...)`, ...); err != nil {
|
|
295
|
-
return err
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
return nil
|
|
299
|
-
})
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Service layer transaction
|
|
303
|
-
func (s *EntityService) Transfer(ctx context.Context, entityID, newParentID uuid.UUID) error {
|
|
304
|
-
tx, err := s.db.Pool().Begin(ctx)
|
|
305
|
-
if err != nil {
|
|
306
|
-
return err
|
|
307
|
-
}
|
|
308
|
-
defer tx.Rollback(ctx)
|
|
309
|
-
|
|
310
|
-
if err := s.entityRepo.UpdateParentTx(ctx, tx, entityID, newParentID); err != nil {
|
|
311
|
-
return err
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
if err := s.auditRepo.LogTransferTx(ctx, tx, entityID, newParentID); err != nil {
|
|
315
|
-
return err
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
return tx.Commit(ctx)
|
|
319
|
-
}
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
## Row Level Security (Multi-tenant)
|
|
323
|
-
|
|
324
|
-
```sql
|
|
325
|
-
-- Enable RLS
|
|
326
|
-
ALTER TABLE entities ENABLE ROW LEVEL SECURITY;
|
|
327
|
-
|
|
328
|
-
-- Policies
|
|
329
|
-
CREATE POLICY tenant_entities ON entities
|
|
330
|
-
USING (tenant_id = current_setting('app.tenant_id')::uuid);
|
|
331
|
-
|
|
332
|
-
CREATE POLICY tenant_entities_insert ON entities
|
|
333
|
-
FOR INSERT WITH CHECK (tenant_id = current_setting('app.tenant_id')::uuid);
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
```go
|
|
337
|
-
// Set tenant context
|
|
338
|
-
func (db *PostgresDB) AcquireWithTenant(ctx context.Context, tenantID string) (*pgxpool.Conn, error) {
|
|
339
|
-
conn, err := db.pool.Acquire(ctx)
|
|
340
|
-
if err != nil {
|
|
341
|
-
return nil, err
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
_, err = conn.Exec(ctx, "SELECT set_config('app.tenant_id', $1, false)", tenantID)
|
|
345
|
-
if err != nil {
|
|
346
|
-
conn.Release()
|
|
347
|
-
return nil, fmt.Errorf("set tenant: %w", err)
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
return conn, nil
|
|
351
|
-
}
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
## Query Builder
|
|
355
|
-
|
|
356
|
-
```go
|
|
357
|
-
type QueryBuilder struct {
|
|
358
|
-
baseQuery string
|
|
359
|
-
conditions []string
|
|
360
|
-
args []interface{}
|
|
361
|
-
argIndex int
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
func NewQueryBuilder(base string) *QueryBuilder {
|
|
365
|
-
return &QueryBuilder{baseQuery: base, conditions: []string{}, args: []interface{}{}}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
func (qb *QueryBuilder) Where(condition string, arg interface{}) *QueryBuilder {
|
|
369
|
-
qb.argIndex++
|
|
370
|
-
qb.conditions = append(qb.conditions, fmt.Sprintf(condition, qb.argIndex))
|
|
371
|
-
qb.args = append(qb.args, arg)
|
|
372
|
-
return qb
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
func (qb *QueryBuilder) WhereIf(cond bool, condition string, arg interface{}) *QueryBuilder {
|
|
376
|
-
if cond { return qb.Where(condition, arg) }
|
|
377
|
-
return qb
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
func (qb *QueryBuilder) Build() (string, []interface{}) {
|
|
381
|
-
query := qb.baseQuery
|
|
382
|
-
if len(qb.conditions) > 0 {
|
|
383
|
-
query += " WHERE " + strings.Join(qb.conditions, " AND ")
|
|
384
|
-
}
|
|
385
|
-
return query, qb.args
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// Usage
|
|
389
|
-
qb := NewQueryBuilder("SELECT * FROM entities").
|
|
390
|
-
Where("tenant_id = $%d", tenantID).
|
|
391
|
-
WhereIf(status != "", "status = $%d", status).
|
|
392
|
-
WhereIf(search != "", "name ILIKE $%d", "%"+search+"%")
|
|
393
|
-
|
|
394
|
-
query, args := qb.Build()
|
|
395
|
-
rows, err := r.pool.Query(ctx, query, args...)
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
## Scanning Patterns
|
|
399
|
-
|
|
400
|
-
```go
|
|
401
|
-
// CollectRows
|
|
402
|
-
func (r *EntityRepository) List(ctx context.Context) ([]*model.Entity, error) {
|
|
403
|
-
rows, err := r.pool.Query(ctx, `SELECT id, name, status FROM entities`)
|
|
404
|
-
if err != nil {
|
|
405
|
-
return nil, err
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
return pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[model.Entity])
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// Custom scanner
|
|
412
|
-
func scanEntity(row pgx.Row) (*model.Entity, error) {
|
|
413
|
-
var e model.Entity
|
|
414
|
-
err := row.Scan(&e.ID, &e.Name, &e.Status, &e.TenantID, &e.CreatedAt, &e.UpdatedAt)
|
|
415
|
-
if err != nil { return nil, err }
|
|
416
|
-
return &e, nil
|
|
417
|
-
}
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
## JSONB Operations
|
|
421
|
-
|
|
422
|
-
```go
|
|
423
|
-
type Entity struct {
|
|
424
|
-
ID uuid.UUID
|
|
425
|
-
Name string
|
|
426
|
-
Metadata map[string]interface{} // JSONB column
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// Insert JSONB
|
|
430
|
-
_, err := r.pool.Exec(ctx,
|
|
431
|
-
`INSERT INTO entities (id, name, metadata) VALUES ($1, $2, $3)`,
|
|
432
|
-
e.ID, e.Name, e.Metadata) // pgx handles map -> JSONB
|
|
433
|
-
|
|
434
|
-
// Query JSONB
|
|
435
|
-
rows, err := r.pool.Query(ctx,
|
|
436
|
-
`SELECT id, name, metadata FROM entities WHERE metadata->>$1 = $2`,
|
|
437
|
-
key, value)
|
|
438
|
-
|
|
439
|
-
// Update JSONB field
|
|
440
|
-
_, err := r.pool.Exec(ctx,
|
|
441
|
-
`UPDATE entities SET metadata = jsonb_set(metadata, $2, $3) WHERE id = $1`,
|
|
442
|
-
id, "{"+key+"}", `"`+value+`"`)
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
## Error Handling
|
|
446
|
-
|
|
447
|
-
```go
|
|
448
|
-
package repository
|
|
449
|
-
|
|
450
|
-
import (
|
|
451
|
-
"errors"
|
|
452
|
-
"github.com/jackc/pgx/v5"
|
|
453
|
-
"github.com/jackc/pgx/v5/pgconn"
|
|
454
|
-
)
|
|
455
|
-
|
|
456
|
-
var (
|
|
457
|
-
ErrNotFound = errors.New("not found")
|
|
458
|
-
ErrDuplicate = errors.New("duplicate entry")
|
|
459
|
-
ErrForeignKey = errors.New("foreign key violation")
|
|
460
|
-
)
|
|
461
|
-
|
|
462
|
-
func wrapError(err error) error {
|
|
463
|
-
if err == nil { return nil }
|
|
464
|
-
|
|
465
|
-
if errors.Is(err, pgx.ErrNoRows) {
|
|
466
|
-
return ErrNotFound
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
var pgErr *pgconn.PgError
|
|
470
|
-
if errors.As(err, &pgErr) {
|
|
471
|
-
switch pgErr.Code {
|
|
472
|
-
case "23505": return ErrDuplicate // unique_violation
|
|
473
|
-
case "23503": return ErrForeignKey // foreign_key_violation
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
return err
|
|
478
|
-
}
|
|
479
|
-
```
|
|
480
|
-
|
|
481
|
-
## Best Practices
|
|
482
|
-
|
|
483
|
-
1. **Always use context**: Include timeout for queries
|
|
484
|
-
```go
|
|
485
|
-
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
486
|
-
defer cancel()
|
|
487
|
-
```
|
|
488
|
-
|
|
489
|
-
2. **Always close rows**: Use defer immediately after Query
|
|
490
|
-
```go
|
|
491
|
-
rows, err := pool.Query(ctx, query)
|
|
492
|
-
if err != nil { return err }
|
|
493
|
-
defer rows.Close()
|
|
494
|
-
```
|
|
495
|
-
|
|
496
|
-
3. **Use pools, not connections**: Pools manage connection lifecycle
|
|
497
|
-
```go
|
|
498
|
-
pool, _ := pgxpool.New(ctx, connString)
|
|
499
|
-
pool.Query(ctx, query) // Pool handles acquire/release
|
|
500
|
-
```
|
|
501
|
-
|
|
502
|
-
4. **Parameterize queries**: Never concatenate user input
|
|
503
|
-
```go
|
|
504
|
-
pool.Query(ctx, "SELECT * FROM users WHERE id = $1", id) // Safe
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
## Related Skills
|
|
508
|
-
|
|
509
|
-
- `chi-router`: HTTP handler integration
|
|
510
|
-
- `redis-cache`: Query result caching
|
|
511
|
-
- `timescaledb`: Time-series extensions
|
|
512
|
-
- `go-backend`: Full Go patterns
|