Fix polymorphic tagging and add test script

This commit fixes the tag loading issue and adds comprehensive testing.

## Bug Fix: Polymorphic Tag Loading

Fixed issue with many-to-many tag relationships not working correctly with
polymorphic associations. The problem was that GORM doesn't support using
`many2many` with polymorphic relationships directly.

**Changes:**
- Modified `internal/models/todo.go`: Changed Tags field to use `gorm:"-"`
  to skip GORM handling
- Modified `internal/models/recurring_todo.go`: Same fix for recurring todos
- Modified `internal/services/todo_service.go`: Added `loadTodoTags()` helper
  function to manually load tags through the taggings join table

**How it works now:**
1. Tags are no longer automatically loaded by GORM
2. Manual loading via JOIN query: `tags JOIN taggings ON tag_id WHERE taggable_id AND taggable_type`
3. Called after loading todos in both `GetTodo()` and `GetTodos()`

## Testing

Added `test_api.sh` - comprehensive integration test script that tests:
1. Health check
2. User registration
3. Authentication
4. Context creation
5. Project creation
6. Todo creation with tags
7. Listing todos with filters
8. Completing todos
9. Project statistics

All tests pass successfully!

## Files Changed

- `internal/models/todo.go`: Fix tag relationship
- `internal/models/recurring_todo.go`: Fix tag relationship
- `internal/services/todo_service.go`: Add manual tag loading
- `test_api.sh`: New integration test script
- `go.sum`: Updated with exact dependency versions
This commit is contained in:
Claude 2025-11-05 10:59:26 +00:00
parent f0eb4bdef5
commit 9b0428638c
No known key found for this signature in database
5 changed files with 212 additions and 7 deletions

View file

@ -72,7 +72,7 @@ type RecurringTodo struct {
Project *Project `gorm:"foreignKey:ProjectID" json:"project,omitempty"`
Todos []Todo `gorm:"foreignKey:RecurringTodoID" json:"todos,omitempty"`
Taggings []Tagging `gorm:"polymorphic:Taggable" json:"-"`
Tags []Tag `gorm:"many2many:taggings;foreignKey:ID;joinForeignKey:TaggableID;References:ID;joinReferences:TagID" json:"tags,omitempty"`
Tags []Tag `gorm:"-" json:"tags,omitempty"`
}
// BeforeCreate sets default values

View file

@ -41,7 +41,7 @@ type Todo struct {
Project *Project `gorm:"foreignKey:ProjectID" json:"project,omitempty"`
RecurringTodo *RecurringTodo `gorm:"foreignKey:RecurringTodoID" json:"recurring_todo,omitempty"`
Taggings []Tagging `gorm:"polymorphic:Taggable" json:"-"`
Tags []Tag `gorm:"many2many:taggings;foreignKey:ID;joinForeignKey:TaggableID;References:ID;joinReferences:TagID" json:"tags,omitempty"`
Tags []Tag `gorm:"-" json:"tags,omitempty"`
Attachments []Attachment `gorm:"foreignKey:TodoID" json:"attachments,omitempty"`
// Dependencies

View file

@ -96,10 +96,6 @@ func (s *TodoService) GetTodos(userID uint, filter ListTodosFilter) ([]models.To
// Preload associations
query = query.Preload("Context").Preload("Project")
if filter.IncludeTags {
query = query.Preload("Tags")
}
// Filter by tag
if filter.TagName != nil {
query = query.Joins("JOIN taggings ON taggings.taggable_id = todos.id AND taggings.taggable_type = ?", "Todo").
@ -114,6 +110,15 @@ func (s *TodoService) GetTodos(userID uint, filter ListTodosFilter) ([]models.To
return nil, err
}
// Load tags if requested
if filter.IncludeTags {
for i := range todos {
if err := s.loadTodoTags(&todos[i]); err != nil {
return nil, err
}
}
}
return todos, nil
}
@ -125,7 +130,6 @@ func (s *TodoService) GetTodo(userID, todoID uint) (*models.Todo, error) {
Where("id = ? AND user_id = ?", todoID, userID).
Preload("Context").
Preload("Project").
Preload("Tags").
Preload("Attachments").
Preload("Predecessors.Predecessor").
Preload("Successors.Successor").
@ -136,9 +140,31 @@ func (s *TodoService) GetTodo(userID, todoID uint) (*models.Todo, error) {
return nil, err
}
// Load tags manually through taggings
if err := s.loadTodoTags(&todo); err != nil {
return nil, err
}
return &todo, nil
}
// loadTodoTags loads tags for a todo through the polymorphic taggings
func (s *TodoService) loadTodoTags(todo *models.Todo) error {
var tags []models.Tag
err := database.DB.
Joins("JOIN taggings ON taggings.tag_id = tags.id").
Where("taggings.taggable_id = ? AND taggings.taggable_type = ?", todo.ID, "Todo").
Find(&tags).Error
if err != nil {
return err
}
todo.Tags = tags
return nil
}
// CreateTodo creates a new todo
func (s *TodoService) CreateTodo(userID uint, req CreateTodoRequest) (*models.Todo, error) {
// Verify context belongs to user