tracks/internal/models/recurring_todo.go

126 lines
4.6 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 (
"time"
"gorm.io/gorm"
)
// RecurrenceType represents the type of recurrence pattern
type RecurrenceType string
const (
RecurrenceTypeDaily RecurrenceType = "daily"
RecurrenceTypeWeekly RecurrenceType = "weekly"
RecurrenceTypeMonthly RecurrenceType = "monthly"
RecurrenceTypeYearly RecurrenceType = "yearly"
)
// RecurringTodoState represents the state of a recurring todo
type RecurringTodoState string
const (
RecurringTodoStateActive RecurringTodoState = "active"
RecurringTodoStateCompleted RecurringTodoState = "completed"
)
// RecurrenceSelector represents how monthly/yearly recurrence is calculated
type RecurrenceSelector string
const (
RecurrenceSelectorDate RecurrenceSelector = "date" // e.g., 15th of month
RecurrenceSelectorDayOfWeek RecurrenceSelector = "day_of_week" // e.g., 3rd Monday
)
// RecurringTodo represents a template for recurring tasks
type RecurringTodo 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"`
Description string `gorm:"not null;size:300" json:"description"`
Notes string `gorm:"type:text" json:"notes"`
State RecurringTodoState `gorm:"type:varchar(20);default:'active'" json:"state"`
RecurrenceType RecurrenceType `gorm:"type:varchar(20);not null" json:"recurrence_type"`
RecurrenceSelector RecurrenceSelector `gorm:"type:varchar(20)" json:"recurrence_selector"`
EveryN int `gorm:"default:1" json:"every_n"` // Every N days/weeks/months/years
StartFrom time.Time `json:"start_from"`
EndsOn *time.Time `json:"ends_on"`
OccurrencesCount *int `json:"occurrences_count"` // Total occurrences to create
NumberOfOccurrences int `gorm:"default:0" json:"number_of_occurrences"` // Created so far
Target string `gorm:"type:varchar(20)" json:"target"` // 'due_date' or 'show_from'
// Weekly recurrence
RecursOnMonday bool `gorm:"default:false" json:"recurs_on_monday"`
RecursOnTuesday bool `gorm:"default:false" json:"recurs_on_tuesday"`
RecursOnWednesday bool `gorm:"default:false" json:"recurs_on_wednesday"`
RecursOnThursday bool `gorm:"default:false" json:"recurs_on_thursday"`
RecursOnFriday bool `gorm:"default:false" json:"recurs_on_friday"`
RecursOnSaturday bool `gorm:"default:false" json:"recurs_on_saturday"`
RecursOnSunday bool `gorm:"default:false" json:"recurs_on_sunday"`
// Daily recurrence
OnlyWorkdays bool `gorm:"default:false" json:"only_workdays"`
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"`
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"`
}
// BeforeCreate sets default values
func (rt *RecurringTodo) BeforeCreate(tx *gorm.DB) error {
if rt.State == "" {
rt.State = RecurringTodoStateActive
}
if rt.EveryN == 0 {
rt.EveryN = 1
}
if rt.Target == "" {
rt.Target = "due_date"
}
return nil
}
// IsActive returns true if the recurring todo is active
func (rt *RecurringTodo) IsActive() bool {
return rt.State == RecurringTodoStateActive
}
// IsCompleted returns true if the recurring todo is completed
func (rt *RecurringTodo) IsCompleted() bool {
return rt.State == RecurringTodoStateCompleted
}
// Complete marks the recurring todo as completed
func (rt *RecurringTodo) Complete() {
rt.State = RecurringTodoStateCompleted
}
// ShouldComplete returns true if the recurring todo has reached its end condition
func (rt *RecurringTodo) ShouldComplete() bool {
// Check if ended by date
if rt.EndsOn != nil && time.Now().After(*rt.EndsOn) {
return true
}
// Check if ended by occurrence count
if rt.OccurrencesCount != nil && rt.NumberOfOccurrences >= *rt.OccurrencesCount {
return true
}
return false
}
// IncrementOccurrences increments the occurrence counter
func (rt *RecurringTodo) IncrementOccurrences() {
rt.NumberOfOccurrences++
}