tracks/internal/models/todo.go

172 lines
4.7 KiB
Go
Raw Normal View History

Rewrite Tracks application in Golang This commit introduces a complete rewrite of the Tracks GTD application in Go (Golang), providing a modern, performant alternative to the Ruby on Rails implementation. ## Architecture & Technology Stack - Language: Go 1.21+ - Web Framework: Gin - ORM: GORM with SQLite/MySQL/PostgreSQL support - Authentication: JWT with bcrypt password hashing - Clean Architecture: Separated models, services, handlers, and middleware ## Implemented Features ### Core Models - User: Authentication and user management - Context: GTD contexts (@home, @work, etc.) - Project: Project grouping and tracking - Todo: Task management with state machine (active, completed, deferred, pending) - Tag: Flexible tagging system with polymorphic associations - Dependency: Todo dependencies with circular dependency detection - Preference: User preferences and settings - Note: Project notes - Attachment: File attachment support (model only) - RecurringTodo: Recurring task template (model only) ### API Endpoints **Authentication:** - POST /api/auth/login - User login - POST /api/auth/register - User registration - POST /api/auth/logout - User logout - GET /api/me - Get current user **Todos:** - GET /api/todos - List todos with filtering - POST /api/todos - Create todo - GET /api/todos/:id - Get todo details - PUT /api/todos/:id - Update todo - DELETE /api/todos/:id - Delete todo - POST /api/todos/:id/complete - Mark as completed - POST /api/todos/:id/activate - Mark as active - POST /api/todos/:id/defer - Defer to future date - POST /api/todos/:id/dependencies - Add dependency - DELETE /api/todos/:id/dependencies/:successor_id - Remove dependency **Projects:** - GET /api/projects - List projects - POST /api/projects - Create project - GET /api/projects/:id - Get project details - PUT /api/projects/:id - Update project - DELETE /api/projects/:id - Delete project - POST /api/projects/:id/complete - Complete project - POST /api/projects/:id/activate - Activate project - POST /api/projects/:id/hide - Hide project - POST /api/projects/:id/review - Mark as reviewed - GET /api/projects/:id/stats - Get project statistics **Contexts:** - GET /api/contexts - List contexts - POST /api/contexts - Create context - GET /api/contexts/:id - Get context details - PUT /api/contexts/:id - Update context - DELETE /api/contexts/:id - Delete context - POST /api/contexts/:id/hide - Hide context - POST /api/contexts/:id/activate - Activate context - POST /api/contexts/:id/close - Close context - GET /api/contexts/:id/stats - Get context statistics ### Business Logic **Todo State Management:** - Active: Ready to work on - Completed: Finished tasks - Deferred: Future actions (show_from date) - Pending: Blocked by dependencies **Dependency Management:** - Create blocking relationships between todos - Automatic state transitions when blocking todos complete - Circular dependency detection - Automatic unblocking when prerequisites complete **Tag System:** - Polymorphic tagging for todos and recurring todos - Automatic tag creation on first use - Tag cloud support **Project & Context Tracking:** - State management (active, hidden, closed/completed) - Statistics and health indicators - Review tracking for projects ### Infrastructure **Configuration:** - Environment-based configuration - Support for SQLite, MySQL, and PostgreSQL - Configurable JWT secrets and token expiry - Flexible server settings **Database:** - GORM for ORM - Automatic migrations - Connection pooling - Multi-database support **Authentication & Security:** - JWT-based authentication - Bcrypt password hashing - Secure cookie support - Token refresh mechanism **Docker Support:** - Multi-stage Dockerfile for optimized builds - Docker Compose with PostgreSQL - Volume mounting for data persistence - Production-ready configuration ## Project Structure ``` cmd/tracks/ # Application entry point internal/ config/ # Configuration management database/ # Database setup and migrations handlers/ # HTTP request handlers middleware/ # Authentication middleware models/ # Database models services/ # Business logic layer ``` ## Documentation - README_GOLANG.md: Comprehensive documentation - .env.example: Configuration template - API documentation included in README - Code comments for complex logic ## Future Work The following features from the original Rails app are not yet implemented: - Recurring todo instantiation logic - Email integration (Mailgun/CloudMailin) - Advanced statistics and analytics - Import/Export functionality (CSV, YAML, XML) - File upload handling for attachments - Mobile views - RSS/Atom feeds - iCalendar export ## Benefits Over Rails Version - Performance: Compiled binary, lower resource usage - Deployment: Single binary, no runtime dependencies - Type Safety: Compile-time type checking - Concurrency: Better handling of concurrent requests - Memory: Lower memory footprint - Portability: Easy cross-platform compilation ## Testing The code structure supports testing, though tests are not yet implemented. Future work includes adding unit and integration tests.
2025-11-05 10:46:59 +00:00
package models
import (
"errors"
"time"
"gorm.io/gorm"
)
// TodoState represents the state of a todo
type TodoState string
const (
TodoStateActive TodoState = "active"
TodoStateCompleted TodoState = "completed"
TodoStateDeferred TodoState = "deferred"
TodoStatePending TodoState = "pending"
)
// Todo represents a task/action item
type Todo struct {
ID uint `gorm:"primaryKey" json:"id"`
UserID uint `gorm:"not null;index" json:"user_id"`
ContextID uint `gorm:"not null;index" json:"context_id"`
ProjectID *uint `gorm:"index" json:"project_id"`
RecurringTodoID *uint `gorm:"index" json:"recurring_todo_id"`
Description string `gorm:"not null;size:300" json:"description"`
Notes string `gorm:"type:text" json:"notes"`
State TodoState `gorm:"type:varchar(20);default:'active';index" json:"state"`
DueDate *time.Time `json:"due_date"`
ShowFrom *time.Time `json:"show_from"`
CompletedAt *time.Time `json:"completed_at"`
Starred bool `gorm:"default:false" json:"starred"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
// Associations
User User `gorm:"foreignKey:UserID" json:"-"`
Context Context `gorm:"foreignKey:ContextID" json:"context,omitempty"`
Project *Project `gorm:"foreignKey:ProjectID" json:"project,omitempty"`
RecurringTodo *RecurringTodo `gorm:"foreignKey:RecurringTodoID" json:"recurring_todo,omitempty"`
Taggings []Tagging `gorm:"polymorphic:Taggable" json:"-"`
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
2025-11-05 10:59:26 +00:00
Tags []Tag `gorm:"-" json:"tags,omitempty"`
Rewrite Tracks application in Golang This commit introduces a complete rewrite of the Tracks GTD application in Go (Golang), providing a modern, performant alternative to the Ruby on Rails implementation. ## Architecture & Technology Stack - Language: Go 1.21+ - Web Framework: Gin - ORM: GORM with SQLite/MySQL/PostgreSQL support - Authentication: JWT with bcrypt password hashing - Clean Architecture: Separated models, services, handlers, and middleware ## Implemented Features ### Core Models - User: Authentication and user management - Context: GTD contexts (@home, @work, etc.) - Project: Project grouping and tracking - Todo: Task management with state machine (active, completed, deferred, pending) - Tag: Flexible tagging system with polymorphic associations - Dependency: Todo dependencies with circular dependency detection - Preference: User preferences and settings - Note: Project notes - Attachment: File attachment support (model only) - RecurringTodo: Recurring task template (model only) ### API Endpoints **Authentication:** - POST /api/auth/login - User login - POST /api/auth/register - User registration - POST /api/auth/logout - User logout - GET /api/me - Get current user **Todos:** - GET /api/todos - List todos with filtering - POST /api/todos - Create todo - GET /api/todos/:id - Get todo details - PUT /api/todos/:id - Update todo - DELETE /api/todos/:id - Delete todo - POST /api/todos/:id/complete - Mark as completed - POST /api/todos/:id/activate - Mark as active - POST /api/todos/:id/defer - Defer to future date - POST /api/todos/:id/dependencies - Add dependency - DELETE /api/todos/:id/dependencies/:successor_id - Remove dependency **Projects:** - GET /api/projects - List projects - POST /api/projects - Create project - GET /api/projects/:id - Get project details - PUT /api/projects/:id - Update project - DELETE /api/projects/:id - Delete project - POST /api/projects/:id/complete - Complete project - POST /api/projects/:id/activate - Activate project - POST /api/projects/:id/hide - Hide project - POST /api/projects/:id/review - Mark as reviewed - GET /api/projects/:id/stats - Get project statistics **Contexts:** - GET /api/contexts - List contexts - POST /api/contexts - Create context - GET /api/contexts/:id - Get context details - PUT /api/contexts/:id - Update context - DELETE /api/contexts/:id - Delete context - POST /api/contexts/:id/hide - Hide context - POST /api/contexts/:id/activate - Activate context - POST /api/contexts/:id/close - Close context - GET /api/contexts/:id/stats - Get context statistics ### Business Logic **Todo State Management:** - Active: Ready to work on - Completed: Finished tasks - Deferred: Future actions (show_from date) - Pending: Blocked by dependencies **Dependency Management:** - Create blocking relationships between todos - Automatic state transitions when blocking todos complete - Circular dependency detection - Automatic unblocking when prerequisites complete **Tag System:** - Polymorphic tagging for todos and recurring todos - Automatic tag creation on first use - Tag cloud support **Project & Context Tracking:** - State management (active, hidden, closed/completed) - Statistics and health indicators - Review tracking for projects ### Infrastructure **Configuration:** - Environment-based configuration - Support for SQLite, MySQL, and PostgreSQL - Configurable JWT secrets and token expiry - Flexible server settings **Database:** - GORM for ORM - Automatic migrations - Connection pooling - Multi-database support **Authentication & Security:** - JWT-based authentication - Bcrypt password hashing - Secure cookie support - Token refresh mechanism **Docker Support:** - Multi-stage Dockerfile for optimized builds - Docker Compose with PostgreSQL - Volume mounting for data persistence - Production-ready configuration ## Project Structure ``` cmd/tracks/ # Application entry point internal/ config/ # Configuration management database/ # Database setup and migrations handlers/ # HTTP request handlers middleware/ # Authentication middleware models/ # Database models services/ # Business logic layer ``` ## Documentation - README_GOLANG.md: Comprehensive documentation - .env.example: Configuration template - API documentation included in README - Code comments for complex logic ## Future Work The following features from the original Rails app are not yet implemented: - Recurring todo instantiation logic - Email integration (Mailgun/CloudMailin) - Advanced statistics and analytics - Import/Export functionality (CSV, YAML, XML) - File upload handling for attachments - Mobile views - RSS/Atom feeds - iCalendar export ## Benefits Over Rails Version - Performance: Compiled binary, lower resource usage - Deployment: Single binary, no runtime dependencies - Type Safety: Compile-time type checking - Concurrency: Better handling of concurrent requests - Memory: Lower memory footprint - Portability: Easy cross-platform compilation ## Testing The code structure supports testing, though tests are not yet implemented. Future work includes adding unit and integration tests.
2025-11-05 10:46:59 +00:00
Attachments []Attachment `gorm:"foreignKey:TodoID" json:"attachments,omitempty"`
// Dependencies
Predecessors []Dependency `gorm:"foreignKey:SuccessorID" json:"predecessors,omitempty"`
Successors []Dependency `gorm:"foreignKey:PredecessorID" json:"successors,omitempty"`
}
// BeforeCreate sets default values
func (t *Todo) BeforeCreate(tx *gorm.DB) error {
if t.State == "" {
t.State = TodoStateActive
}
return nil
}
// IsActive returns true if the todo is active
func (t *Todo) IsActive() bool {
return t.State == TodoStateActive
}
// IsCompleted returns true if the todo is completed
func (t *Todo) IsCompleted() bool {
return t.State == TodoStateCompleted
}
// IsDeferred returns true if the todo is deferred
func (t *Todo) IsDeferred() bool {
return t.State == TodoStateDeferred
}
// IsPending returns true if the todo is pending (blocked)
func (t *Todo) IsPending() bool {
return t.State == TodoStatePending
}
// Complete transitions the todo to completed state
func (t *Todo) Complete() error {
if t.IsCompleted() {
return errors.New("todo is already completed")
}
now := time.Now()
t.State = TodoStateCompleted
t.CompletedAt = &now
return nil
}
// Activate transitions the todo to active state
func (t *Todo) Activate() error {
if t.IsActive() {
return errors.New("todo is already active")
}
// Can't activate if it has incomplete predecessors
// This check should be done by the service layer
t.State = TodoStateActive
t.CompletedAt = nil
return nil
}
// Defer transitions the todo to deferred state
func (t *Todo) Defer(showFrom time.Time) error {
if !t.IsActive() {
return errors.New("can only defer active todos")
}
t.State = TodoStateDeferred
t.ShowFrom = &showFrom
return nil
}
// Block transitions the todo to pending state
func (t *Todo) Block() error {
if t.IsCompleted() {
return errors.New("cannot block completed todo")
}
t.State = TodoStatePending
return nil
}
// Unblock transitions the todo from pending to active
func (t *Todo) Unblock() error {
if !t.IsPending() {
return errors.New("todo is not pending")
}
t.State = TodoStateActive
return nil
}
// IsDue returns true if the todo has a due date that has passed
func (t *Todo) IsDue() bool {
if t.DueDate == nil {
return false
}
return t.DueDate.Before(time.Now())
}
// IsOverdue returns true if the todo is active and past due
func (t *Todo) IsOverdue() bool {
return t.IsActive() && t.IsDue()
}
// ShouldShow returns true if the todo should be displayed (not deferred or show_from has passed)
func (t *Todo) ShouldShow() bool {
if t.ShowFrom == nil {
return true
}
return t.ShowFrom.Before(time.Now()) || t.ShowFrom.Equal(time.Now())
}
// IsStale returns true if the todo is old based on the staleness threshold
func (t *Todo) IsStale(stalenessThresholdDays int) bool {
if t.IsCompleted() {
return false
}
threshold := time.Now().AddDate(0, 0, -stalenessThresholdDays)
return t.CreatedAt.Before(threshold)
}