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:"-"`
|
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)
|
|
|
|
|
}
|