🎉 Initial commit: Trackeep - Complete Productivity Platform

🚀 Features Implemented:
 Full-stack application with SolidJS frontend + Go backend
 User authentication with JWT tokens
 Bookmark management with tags and search
 Task management with status and priority tracking
 File upload and management system
 Notes with rich text editing and organization
 Advanced search and filtering across all content types
 Export/import functionality for data portability

🏗️ Architecture:
- Frontend: SolidJS + TypeScript + UnoCSS + TanStack Query
- Backend: Go + Gin + GORM + PostgreSQL/SQLite
- Deployment: Docker + Docker Compose + CI/CD pipeline
- Monitoring: Structured logging + metrics collection + health checks

📦 Production Ready:
 Multi-stage Docker builds for frontend and backend
 Production docker-compose with Redis and backup services
 GitHub Actions CI/CD pipeline with security scanning
 Comprehensive logging and monitoring system
 Automated backup and recovery strategies
 Complete API documentation and user guide

📚 Documentation:
- Complete API documentation with examples
- Comprehensive user guide with troubleshooting
- Deployment and configuration instructions
- Security best practices and performance optimization

🎯 Project Status: 100% COMPLETE (69/69 tasks)
Trackeep is now a production-ready, self-hosted productivity platform!
This commit is contained in:
Tomas Dvorak
2026-01-26 12:36:49 +01:00
commit 18aa702174
79 changed files with 12885 additions and 0 deletions
+226
View File
@@ -0,0 +1,226 @@
package middleware
import (
"sync"
"time"
"github.com/gin-gonic/gin"
)
// Metrics holds application metrics
type Metrics struct {
mu sync.RWMutex
// HTTP metrics
RequestsTotal map[string]int64
RequestsDuration map[string][]time.Duration
RequestsErrors map[string]int64
ActiveConnections int64
// Application metrics
UsersTotal int64
BookmarksTotal int64
TasksTotal int64
FilesTotal int64
NotesTotal int64
// System metrics
DatabaseConnections int64
LastRestart time.Time
}
var (
// Global metrics instance
appMetrics = &Metrics{
RequestsTotal: make(map[string]int64),
RequestsDuration: make(map[string][]time.Duration),
RequestsErrors: make(map[string]int64),
LastRestart: time.Now(),
}
)
// GetMetrics returns the current metrics
func GetMetrics() *Metrics {
appMetrics.mu.RLock()
defer appMetrics.mu.RUnlock()
// Return a copy to avoid concurrent access issues
return &Metrics{
RequestsTotal: copyMap(appMetrics.RequestsTotal),
RequestsDuration: copyDurationMap(appMetrics.RequestsDuration),
RequestsErrors: copyMap(appMetrics.RequestsErrors),
ActiveConnections: appMetrics.ActiveConnections,
UsersTotal: appMetrics.UsersTotal,
BookmarksTotal: appMetrics.BookmarksTotal,
TasksTotal: appMetrics.TasksTotal,
FilesTotal: appMetrics.FilesTotal,
NotesTotal: appMetrics.NotesTotal,
DatabaseConnections: appMetrics.DatabaseConnections,
LastRestart: appMetrics.LastRestart,
}
}
// MetricsMiddleware collects HTTP metrics
func MetricsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
method := c.Request.Method
// Increment active connections
appMetrics.mu.Lock()
appMetrics.ActiveConnections++
appMetrics.mu.Unlock()
// Process request
c.Next()
// Decrement active connections
appMetrics.mu.Lock()
appMetrics.ActiveConnections--
appMetrics.mu.Unlock()
// Calculate duration
duration := time.Since(start)
// Update metrics
appMetrics.mu.Lock()
defer appMetrics.mu.Unlock()
key := method + " " + path
// Increment total requests
appMetrics.RequestsTotal[key]++
// Record duration
if appMetrics.RequestsDuration[key] == nil {
appMetrics.RequestsDuration[key] = make([]time.Duration, 0, 1000)
}
appMetrics.RequestsDuration[key] = append(appMetrics.RequestsDuration[key], duration)
// Keep only last 1000 duration records per endpoint
if len(appMetrics.RequestsDuration[key]) > 1000 {
appMetrics.RequestsDuration[key] = appMetrics.RequestsDuration[key][1:]
}
// Count errors
if c.Writer.Status() >= 400 {
appMetrics.RequestsErrors[key]++
}
}
}
// IncrementUsersTotal increments the total users count
func IncrementUsersTotal() {
appMetrics.mu.Lock()
defer appMetrics.mu.Unlock()
appMetrics.UsersTotal++
}
// IncrementBookmarksTotal increments the total bookmarks count
func IncrementBookmarksTotal() {
appMetrics.mu.Lock()
defer appMetrics.mu.Unlock()
appMetrics.BookmarksTotal++
}
// DecrementBookmarksTotal decrements the total bookmarks count
func DecrementBookmarksTotal() {
appMetrics.mu.Lock()
defer appMetrics.mu.Unlock()
if appMetrics.BookmarksTotal > 0 {
appMetrics.BookmarksTotal--
}
}
// IncrementTasksTotal increments the total tasks count
func IncrementTasksTotal() {
appMetrics.mu.Lock()
defer appMetrics.mu.Unlock()
appMetrics.TasksTotal++
}
// DecrementTasksTotal decrements the total tasks count
func DecrementTasksTotal() {
appMetrics.mu.Lock()
defer appMetrics.mu.Unlock()
if appMetrics.TasksTotal > 0 {
appMetrics.TasksTotal--
}
}
// IncrementFilesTotal increments the total files count
func IncrementFilesTotal() {
appMetrics.mu.Lock()
defer appMetrics.mu.Unlock()
appMetrics.FilesTotal++
}
// DecrementFilesTotal decrements the total files count
func DecrementFilesTotal() {
appMetrics.mu.Lock()
defer appMetrics.mu.Unlock()
if appMetrics.FilesTotal > 0 {
appMetrics.FilesTotal--
}
}
// IncrementNotesTotal increments the total notes count
func IncrementNotesTotal() {
appMetrics.mu.Lock()
defer appMetrics.mu.Unlock()
appMetrics.NotesTotal++
}
// DecrementNotesTotal decrements the total notes count
func DecrementNotesTotal() {
appMetrics.mu.Lock()
defer appMetrics.mu.Unlock()
if appMetrics.NotesTotal > 0 {
appMetrics.NotesTotal--
}
}
// SetDatabaseConnections sets the database connections count
func SetDatabaseConnections(count int64) {
appMetrics.mu.Lock()
defer appMetrics.mu.Unlock()
appMetrics.DatabaseConnections = count
}
// ResetMetrics resets all metrics (useful for testing)
func ResetMetrics() {
appMetrics.mu.Lock()
defer appMetrics.mu.Unlock()
appMetrics.RequestsTotal = make(map[string]int64)
appMetrics.RequestsDuration = make(map[string][]time.Duration)
appMetrics.RequestsErrors = make(map[string]int64)
appMetrics.ActiveConnections = 0
appMetrics.UsersTotal = 0
appMetrics.BookmarksTotal = 0
appMetrics.TasksTotal = 0
appMetrics.FilesTotal = 0
appMetrics.NotesTotal = 0
appMetrics.DatabaseConnections = 0
appMetrics.LastRestart = time.Now()
}
// Helper functions to copy maps safely
func copyMap(original map[string]int64) map[string]int64 {
copy := make(map[string]int64)
for k, v := range original {
copy[k] = v
}
return copy
}
func copyDurationMap(original map[string][]time.Duration) map[string][]time.Duration {
result := make(map[string][]time.Duration)
for k, v := range original {
sliceCopy := make([]time.Duration, len(v))
copy(sliceCopy, v)
result[k] = sliceCopy
}
return result
}