Migrate from Gin to Echo framework and add comprehensive integration tests
Major changes: - Migrate all handlers from Gin to Echo framework - Add new apperrors, echohelpers, and validator packages - Update middleware for Echo compatibility - Add ArchivedHandler to task categorization chain (archived tasks go to cancelled_tasks column) - Add 6 new integration tests: - RecurringTaskLifecycle: NextDueDate advancement for weekly/monthly tasks - MultiUserSharing: Complex sharing with user removal - TaskStateTransitions: All state transitions and kanban column changes - DateBoundaryEdgeCases: Threshold boundary testing - CascadeOperations: Residence deletion cascade effects - MultiUserOperations: Shared residence collaboration - Add single-purpose repository functions for kanban columns (GetOverdueTasks, GetDueSoonTasks, etc.) - Fix RemoveUser route param mismatch (userId -> user_id) - Fix determineExpectedColumn helper to correctly prioritize in_progress over overdue 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
114
migrate_handlers.py
Normal file
114
migrate_handlers.py
Normal file
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Migrate Gin handlers to Echo handlers
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
|
||||
def migrate_handler_file(content):
|
||||
"""Migrate a single handler file from Gin to Echo"""
|
||||
|
||||
# 1. Import changes
|
||||
content = re.sub(r'"github\.com/gin-gonic/gin"', '"github.com/labstack/echo/v4"', content)
|
||||
|
||||
# Add validator import if not present
|
||||
if '"github.com/treytartt/casera-api/internal/validator"' not in content:
|
||||
content = re.sub(
|
||||
r'("github\.com/treytartt/casera-api/internal/services"\n)',
|
||||
r'\1\t"github.com/treytartt/casera-api/internal/validator"\n',
|
||||
content
|
||||
)
|
||||
|
||||
# 2. Handler signatures - must return error
|
||||
content = re.sub(
|
||||
r'func \(h \*(\w+Handler)\) (\w+)\(c \*gin\.Context\) {',
|
||||
r'func (h *\1) \2(c echo.Context) error {',
|
||||
content
|
||||
)
|
||||
|
||||
# 3. c.MustGet -> c.Get
|
||||
content = re.sub(r'c\.MustGet\(', 'c.Get(', content)
|
||||
|
||||
# 4. Bind and validate separately
|
||||
# Match ShouldBindJSON pattern and replace with Bind + Validate
|
||||
def replace_bind_validate(match):
|
||||
indent = match.group(1)
|
||||
var_name = match.group(2)
|
||||
error_block = match.group(3)
|
||||
|
||||
# Replace the error block to use 'return'
|
||||
error_block_fixed = re.sub(r'\n(\t+)c\.JSON\(', r'\n\1return c.JSON(', error_block)
|
||||
error_block_fixed = re.sub(r'\n(\t+)\}\n(\t+)return\n', r'\n\1}\n', error_block_fixed)
|
||||
|
||||
return (f'{indent}if err := c.Bind(&{var_name}); err != nil {{\n{error_block_fixed}'
|
||||
f'{indent}}}\n'
|
||||
f'{indent}if err := c.Validate(&{var_name}); err != nil {{\n'
|
||||
f'{indent}\treturn c.JSON(http.StatusBadRequest, validator.FormatValidationErrors(err))\n'
|
||||
f'{indent}}}')
|
||||
|
||||
# Handle ShouldBindJSON with error handling
|
||||
content = re.sub(
|
||||
r'(\t+)if err := c\.ShouldBindJSON\(&(\w+)\); err != nil \{((?:\n(?:\1\t.*|))*\n\1\}\n\1\treturn\n)',
|
||||
replace_bind_validate,
|
||||
content
|
||||
)
|
||||
|
||||
# Handle optional ShouldBindJSON (no error check)
|
||||
content = re.sub(r'c\.ShouldBindJSON\(&(\w+)\)', r'c.Bind(&\1)', content)
|
||||
|
||||
# 5. gin.H -> map[string]interface{}
|
||||
content = re.sub(r'gin\.H\{', 'map[string]interface{}{', content)
|
||||
|
||||
# 6. c.Query -> c.QueryParam
|
||||
content = re.sub(r'c\.Query\(', 'c.QueryParam(', content)
|
||||
|
||||
# 7. c.PostForm -> c.FormValue
|
||||
content = re.sub(r'c\.PostForm\(', 'c.FormValue(', content)
|
||||
|
||||
# 8. c.GetHeader -> c.Request().Header.Get
|
||||
content = re.sub(r'c\.GetHeader\(', 'c.Request().Header.Get(', content)
|
||||
|
||||
# 9. c.Request.Context() -> c.Request().Context()
|
||||
content = re.sub(r'c\.Request\.Context\(\)', 'c.Request().Context()', content)
|
||||
|
||||
# 10. All c.JSON, c.Status calls must have 'return'
|
||||
# Match c.JSON without return
|
||||
content = re.sub(
|
||||
r'(\n\t+)c\.JSON\(([^)]+\))',
|
||||
r'\1return c.JSON(\2',
|
||||
content
|
||||
)
|
||||
|
||||
# Match c.Status -> c.NoContent
|
||||
content = re.sub(
|
||||
r'(\n\t+)c\.Status\(([^)]+)\)',
|
||||
r'\1return c.NoContent(\2)',
|
||||
content
|
||||
)
|
||||
|
||||
# 11. Fix double 'return return' issues
|
||||
content = re.sub(r'return return c\.', 'return c.', content)
|
||||
|
||||
# 12. Remove standalone 'return' at end of functions (now returns values)
|
||||
# This is tricky - we need to remove lines that are just '\treturn\n}' at function end
|
||||
content = re.sub(r'\n\treturn\n\}$', r'\n}', content, flags=re.MULTILINE)
|
||||
content = re.sub(r'\n(\t+)return\n(\1)\}', r'\n\2}', content)
|
||||
|
||||
return content
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: migrate_handlers.py <handler_file.go>")
|
||||
sys.exit(1)
|
||||
|
||||
filename = sys.argv[1]
|
||||
|
||||
with open(filename, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
migrated = migrate_handler_file(content)
|
||||
|
||||
with open(filename, 'w') as f:
|
||||
f.write(migrated)
|
||||
|
||||
print(f"Migrated {filename}")
|
||||
Reference in New Issue
Block a user