mirror of
https://github.com/Dvorinka/Trackeep.git
synced 2026-06-04 04:22:57 +00:00
18aa702174
🚀 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!
227 lines
5.5 KiB
Go
227 lines
5.5 KiB
Go
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
|
|
}
|