Quick Reference: For workflow overview, see CLAUDE.md
This document provides step-by-step implementation guides for common development tasks across the VoIPbin monorepo.
- Define OpenAPI spec in
bin-openapi-manager/openapi/paths/<resource>/ - Regenerate models:
cd bin-openapi-manager && go generate ./... - Add handler in target service's
pkg/listenhandler/ - Implement business logic in appropriate domain handler
- Register endpoint in
bin-api-managerif public-facing - Update Swagger docs in api-manager:
swag init
- Define action type in
bin-flow-manager/models/action/ - Add action handler in
bin-flow-manager/pkg/actionhandler/ - Implement execution logic in target service (e.g., call-manager)
- Update flow-manager's request handler to route to new service
- Add tests for action validation and execution
- Create directory:
bin-<name>-manager/ - Copy structure from existing service (e.g., tag-manager)
- Update
bin-api-manager/go.modwith replace directive - Add RabbitMQ queue names to
bin-common-handler/models/outline/ - Add request methods to
bin-common-handler/pkg/requesthandler/ - Update
.circleci/config.ymlpath mappings
When changing bin-common-handler or bin-openapi-manager:
- Make changes in the shared package
- Regenerate if needed:
go generate ./... - Test impact on dependent services
- Coordinate deployment - shared changes affect multiple services
Note: For flow execution patterns and variable substitution, see bin-flow-manager/CLAUDE.md.
Note: This is a monorepo-wide pattern for all services that expose list endpoints (GET /v1/resources). Implemented using bin-common-handler/pkg/utilhandler. See individual service CLAUDE.md files for service-specific filter field definitions.
All list endpoints parse filters from request body (JSON), not URL query parameters.
This pattern was implemented as part of the commondatabasehandler refactoring. See docs/plans/2026-01-14-listenhandler-filter-parsing-implementation-plan.md for complete implementation details.
π¨ CRITICAL RULE: NEVER parse filter data from URL query parameters
ONLY pagination parameters (page_size, page_token) should be parsed from URL query parameters:
// β
CORRECT - Parse pagination from URL
tmpSize, _ := strconv.Atoi(u.Query().Get(PageSize))
pageSize := uint64(tmpSize)
pageToken := u.Query().Get(PageToken)
// β
CORRECT - Parse filters from request body
tmpFilters, err := utilhandler.ParseFiltersFromRequestBody(m.Data)
typedFilters, err := utilhandler.ConvertFilters[resource.FieldStruct, resource.Field](...)// β WRONG - Never parse filter data from URL
aicallID := uuid.FromStringOrNil(u.Query().Get("aicall_id")) // BUG!
customerID := uuid.FromStringOrNil(u.Query().Get("customer_id")) // BUG!
// These will be uuid.Nil because requesthandler sends them in body, not URL
// This causes empty query results and is a common bug patternWhy this is critical:
bin-common-handler/pkg/requesthandlersends ALL filters in request body, not URL- Parsing from URL gets
uuid.Nilor empty strings β database queries return empty results - This bug was found in
bin-ai-manager/v1_messages.go(seedocs/plans/2026-01-16-fix-aimessages-empty-list-bug-design.md)
Reference implementation: bin-agent-manager/pkg/listenhandler/v1_agents.go:20-53
Step 1: Define FieldStruct in model package
Create or update models/<resource>/filters.go:
package <resource>
import "github.com/gofrs/uuid"
type FieldStruct struct {
CustomerID uuid.UUID `filter:"customer_id"`
Deleted bool `filter:"deleted"`
Name string `filter:"name"`
// ... other filterable fields
}Step 2: Parse filters in listenhandler
In pkg/listenhandler/v1_<resource>s.go:
func (h *listenHandler) processV1<Resource>sGet(ctx context.Context, m sock.Request) (sock.Response, error) {
// Parse pagination from URL (unchanged)
u, err := url.Parse(m.URI)
// ...
// Parse filters from request body (NEW)
tmpFilters, err := h.utilHandler.ParseFiltersFromRequestBody(m.Data)
if err != nil {
log.Errorf("Could not parse filters. err: %v", err)
return simpleResponse(400), nil
}
// Convert to typed filters using FieldStruct
typedFilters, err := h.utilHandler.ConvertFilters[<resource>.FieldStruct, <resource>.Field](
<resource>.FieldStruct{},
tmpFilters,
)
if err != nil {
log.Errorf("Could not convert filters. err: %v", err)
return simpleResponse(400), nil
}
// Use typedFilters with dbhandler
items, err := h.<resource>Handler.<Resource>GetAll(ctx, typedFilters, pageOpts)
// ...
}Step 3: Make requests with body filters
External API calls (via bin-api-manager):
curl -X GET https://api.voipbin.net/v1/conversations \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "5e4a0680-804e-11ec-8477-2fea5968d85b",
"deleted": false,
"limit": 50
}'Internal RPC calls (via requesthandler):
// requesthandler already sends filters in body automatically
conversations, err := reqHandler.ConversationV1ConversationGetAll(ctx, filters, pageOpts)- Consistency - Matches how
requesthandlerhas always sent filters (in body) - Type safety - FieldStruct enables compile-time validation and type conversion
- Complex filters - Body JSON supports nested structures, URL params don't
- No URL length limits - Avoids issues with long filter strings
- Old pattern: Filters in URL query params (e.g.,
?customer_id=xxx&deleted=false) - New pattern: Filters in request body JSON
- Pagination still in URL:
?page=1&limit=50
For full implementation details, see:
bin-common-handler/pkg/utilhandler/filters.go- Generic filter parsing functionsdocs/plans/2026-01-14-listenhandler-filter-parsing-implementation-plan.md- Complete implementation guide
Database schema changes for VoIPbin services are managed through Alembic migrations in the bin-dbscheme-manager service. This is the ONLY way to modify database schemas - never run manual SQL DDL statements against production databases.
What AI CAN do:
- β
Create migration files (
alembic -c alembic.ini revision -m "...") - β Edit migration files to add SQL in upgrade()/downgrade() functions
- β Read and explain migration file contents
- β Commit migration files to git
What AI MUST NEVER do:
- π« Run
alembic upgrade(applies migrations to database) - π« Run
alembic downgrade(rolls back database changes) - π« Execute any SQL that modifies database schema
- π« Apply migrations automatically
Why: Database schema changes are irreversible operations requiring:
- Human review and explicit authorization
- Testing on development environment first
- VPN access to production database
- Coordination with deployment schedule
- Risk of data loss or service outages
AI can assist with creating and reviewing migration files, but database execution requires human control.
Create an Alembic migration whenever you:
- Add new columns to existing tables
- Create new tables
- Modify column types or constraints
- Add/remove indexes
- Change foreign key relationships
Common scenarios:
- Adding fields to a model (e.g., adding
owner_typecolumn tostorage_filestable) - Creating tables for new features
- Database refactoring to support new functionality
Step 1: Navigate to bin-dbscheme-manager
cd bin-dbscheme-manager/bin-managerStep 2: Configure Alembic (first time only)
# Copy sample config
cp alembic.ini.sample alembic.ini
# Edit alembic.ini and set database connection
# sqlalchemy.url = mysql://user:pass@host/voipbinStep 3: Create migration file
# Use descriptive naming: <table>_<action>_<type>_<items>
alembic -c alembic.ini revision -m "storage_files add column owner_type"This creates a new file in main/versions/ with format: <hash>_storage_files_add_column_owner_type.py
Step 4: Edit migration file
Edit the generated file to add your SQL changes:
def upgrade():
op.execute("""
ALTER TABLE storage_files
ADD COLUMN owner_type VARCHAR(255) DEFAULT '' AFTER owner_id
""")
def downgrade():
op.execute("""
ALTER TABLE storage_files
DROP COLUMN owner_type
""")Step 5: Commit and push (AI can do this)
cd /home/pchero/gitvoipbin/monorepo
git add bin-dbscheme-manager/bin-manager/main/versions/<hash>_*.py
git commit -m "feat(dbscheme): add owner_type column to storage_files table"
git pushStep 6: (HUMAN ONLY) Test migration locally
# π« AI MUST NOT execute these commands
# Apply migration to local database
alembic -c alembic.ini upgrade head
# Verify schema change
mysql -u user -p voipbin -e "DESCRIBE storage_files;"
# Test rollback (optional)
alembic -c alembic.ini downgrade -1
alembic -c alembic.ini upgrade headStep 7: (HUMAN ONLY) Apply to staging/production
Connect to VPN first (REQUIRED), then apply migration:
# π« AI MUST NOT execute these commands
cd bin-dbscheme-manager/bin-manager
alembic -c alembic.ini upgrade head- Always add rollback logic - Implement both
upgrade()anddowngrade()functions - Use raw SQL - Migrations use
op.execute()with raw SQL, not SQLAlchemy DDL - Test locally first - Never run untested migrations against staging/production
- One change per migration - Keep migrations focused and atomic
- Descriptive names - Follow naming convention:
<table>_<action>_<type>_<items> - Coordinate with code - Deploy code changes that depend on schema changes AFTER migration runs
Add column:
def upgrade():
op.execute("""
ALTER TABLE <table_name>
ADD COLUMN <column_name> <type> DEFAULT <default_value> AFTER <existing_column>
""")
def downgrade():
op.execute("""
ALTER TABLE <table_name>
DROP COLUMN <column_name>
""")Create table:
def upgrade():
op.execute("""
CREATE TABLE <table_name> (
id BINARY(16) NOT NULL PRIMARY KEY,
customer_id BINARY(16) NOT NULL,
name VARCHAR(255) NOT NULL,
tm_create DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
INDEX idx_customer_id (customer_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
""")
def downgrade():
op.execute("""DROP TABLE <table_name>""")Issue: "Target database is not up to date"
# Check current migration version
alembic -c alembic.ini current
# View migration history
alembic -c alembic.ini history
# Apply missing migrations
alembic -c alembic.ini upgrade headIssue: Migration fails to apply
- Check database connection in
alembic.ini - Verify VPN connection for remote databases
- Check SQL syntax in migration file
- Review error messages for constraint violations
Issue: Need to rollback migration
# Rollback last migration
alembic -c alembic.ini downgrade -1
# Rollback to specific revision
alembic -c alembic.ini downgrade <revision_id>- VPN connection is MANDATORY for staging/production migrations
- Never modify applied migrations - create new ones instead
- Coordinate schema changes with dependent services
- Test migrations against realistic data volumes
- Document breaking changes in migration docstrings
- See
bin-dbscheme-manager/CLAUDE.mdfor service-specific details