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>
115 lines
3.8 KiB
Python
115 lines
3.8 KiB
Python
#!/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}")
|