mirror of
https://github.com/TracksApp/tracks.git
synced 2026-02-01 21:51:49 +01:00
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.
This commit is contained in:
parent
6613d33f10
commit
f0eb4bdef5
29 changed files with 4100 additions and 104 deletions
147
cmd/tracks/main.go
Normal file
147
cmd/tracks/main.go
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/TracksApp/tracks/internal/config"
|
||||
"github.com/TracksApp/tracks/internal/database"
|
||||
"github.com/TracksApp/tracks/internal/handlers"
|
||||
"github.com/TracksApp/tracks/internal/middleware"
|
||||
"github.com/TracksApp/tracks/internal/services"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Load configuration
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
log.Fatal("Failed to load configuration:", err)
|
||||
}
|
||||
|
||||
// Initialize database
|
||||
if err := database.Initialize(&cfg.Database); err != nil {
|
||||
log.Fatal("Failed to initialize database:", err)
|
||||
}
|
||||
defer database.Close()
|
||||
|
||||
// Run migrations
|
||||
if err := database.AutoMigrate(); err != nil {
|
||||
log.Fatal("Failed to run migrations:", err)
|
||||
}
|
||||
|
||||
// Set Gin mode
|
||||
gin.SetMode(cfg.Server.Mode)
|
||||
|
||||
// Create router
|
||||
router := gin.Default()
|
||||
|
||||
// Setup routes
|
||||
setupRoutes(router, cfg)
|
||||
|
||||
// Start server
|
||||
addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port)
|
||||
log.Printf("Starting Tracks server on %s", addr)
|
||||
if err := router.Run(addr); err != nil {
|
||||
log.Fatal("Failed to start server:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func setupRoutes(router *gin.Engine, cfg *config.Config) {
|
||||
// Initialize services
|
||||
authService := services.NewAuthService(cfg.Auth.JWTSecret)
|
||||
todoService := services.NewTodoService()
|
||||
projectService := services.NewProjectService()
|
||||
contextService := services.NewContextService()
|
||||
|
||||
// Initialize handlers
|
||||
authHandler := handlers.NewAuthHandler(authService)
|
||||
todoHandler := handlers.NewTodoHandler(todoService)
|
||||
projectHandler := handlers.NewProjectHandler(projectService)
|
||||
contextHandler := handlers.NewContextHandler(contextService)
|
||||
|
||||
// Public routes
|
||||
api := router.Group("/api")
|
||||
{
|
||||
// Health check
|
||||
api.GET("/health", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"status": "ok"})
|
||||
})
|
||||
|
||||
// Auth routes
|
||||
auth := api.Group("/auth")
|
||||
{
|
||||
auth.POST("/login", authHandler.Login)
|
||||
auth.POST("/register", authHandler.Register)
|
||||
auth.POST("/logout", authHandler.Logout)
|
||||
}
|
||||
}
|
||||
|
||||
// Protected routes
|
||||
protected := api.Group("")
|
||||
protected.Use(middleware.AuthMiddleware(cfg.Auth.JWTSecret))
|
||||
{
|
||||
// User routes
|
||||
protected.GET("/me", authHandler.Me)
|
||||
protected.POST("/refresh-token", authHandler.RefreshToken)
|
||||
|
||||
// Todo routes
|
||||
todos := protected.Group("/todos")
|
||||
{
|
||||
todos.GET("", todoHandler.ListTodos)
|
||||
todos.POST("", todoHandler.CreateTodo)
|
||||
todos.GET("/:id", todoHandler.GetTodo)
|
||||
todos.PUT("/:id", todoHandler.UpdateTodo)
|
||||
todos.DELETE("/:id", todoHandler.DeleteTodo)
|
||||
todos.POST("/:id/complete", todoHandler.CompleteTodo)
|
||||
todos.POST("/:id/activate", todoHandler.ActivateTodo)
|
||||
todos.POST("/:id/defer", todoHandler.DeferTodo)
|
||||
todos.POST("/:id/dependencies", todoHandler.AddDependency)
|
||||
todos.DELETE("/:id/dependencies/:successor_id", todoHandler.RemoveDependency)
|
||||
}
|
||||
|
||||
// Project routes
|
||||
projects := protected.Group("/projects")
|
||||
{
|
||||
projects.GET("", projectHandler.ListProjects)
|
||||
projects.POST("", projectHandler.CreateProject)
|
||||
projects.GET("/:id", projectHandler.GetProject)
|
||||
projects.PUT("/:id", projectHandler.UpdateProject)
|
||||
projects.DELETE("/:id", projectHandler.DeleteProject)
|
||||
projects.POST("/:id/complete", projectHandler.CompleteProject)
|
||||
projects.POST("/:id/activate", projectHandler.ActivateProject)
|
||||
projects.POST("/:id/hide", projectHandler.HideProject)
|
||||
projects.POST("/:id/review", projectHandler.MarkReviewed)
|
||||
projects.GET("/:id/stats", projectHandler.GetProjectStats)
|
||||
}
|
||||
|
||||
// Context routes
|
||||
contexts := protected.Group("/contexts")
|
||||
{
|
||||
contexts.GET("", contextHandler.ListContexts)
|
||||
contexts.POST("", contextHandler.CreateContext)
|
||||
contexts.GET("/:id", contextHandler.GetContext)
|
||||
contexts.PUT("/:id", contextHandler.UpdateContext)
|
||||
contexts.DELETE("/:id", contextHandler.DeleteContext)
|
||||
contexts.POST("/:id/hide", contextHandler.HideContext)
|
||||
contexts.POST("/:id/activate", contextHandler.ActivateContext)
|
||||
contexts.POST("/:id/close", contextHandler.CloseContext)
|
||||
contexts.GET("/:id/stats", contextHandler.GetContextStats)
|
||||
}
|
||||
}
|
||||
|
||||
// CORS middleware for development
|
||||
router.Use(func(c *gin.Context) {
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE, PATCH")
|
||||
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(204)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue