Initial commit: Project foundation

- Backend: Go API server with Gin framework
- Frontend: React setup (placeholder)
- ML Service: Python FastAPI skeleton
- Docker Compose: Full stack configuration
- Database: PostgreSQL schema with migrations
- Documentation: Implementation plan and README

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude Code
2026-02-05 00:44:11 +03:00
commit 7043429150
24 changed files with 2359 additions and 0 deletions

View File

@@ -0,0 +1,186 @@
package api
import (
"database/sql"
"net/http"
"github.com/gin-gonic/gin"
"github.com/yourusername/victorialogs-manager/internal/config"
)
// NewRouter creates and configures the main router
func NewRouter(cfg *config.Config, db *sql.DB) *gin.Engine {
// Set Gin mode
gin.SetMode(gin.ReleaseMode)
router := gin.Default()
// CORS middleware
router.Use(corsMiddleware())
// Health check endpoint
router.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"status": "ok",
"service": "victorialogs-manager",
})
})
// API v1 routes
v1 := router.Group("/api/v1")
{
// Authentication routes (public)
auth := v1.Group("/auth")
{
auth.POST("/login", func(c *gin.Context) {
// TODO: Implement login handler
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
auth.POST("/refresh", func(c *gin.Context) {
// TODO: Implement refresh handler
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
auth.GET("/me", func(c *gin.Context) {
// TODO: Implement me handler (requires auth)
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
}
// Protected routes (require authentication)
// TODO: Add auth middleware here
// User management routes
users := v1.Group("/users")
{
users.GET("", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
users.POST("", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
users.GET("/:id", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
users.PUT("/:id", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
users.DELETE("/:id", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
}
// Log querying routes
logs := v1.Group("/logs")
{
logs.POST("/query", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
logs.POST("/tail", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
logs.GET("/facets", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
logs.POST("/stats", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
logs.POST("/export", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
}
// Alert management routes
alerts := v1.Group("/alerts")
{
alerts.GET("/rules", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
alerts.POST("/rules", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
alerts.GET("/rules/:id", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
alerts.PUT("/rules/:id", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
alerts.DELETE("/rules/:id", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
alerts.POST("/reload", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
alerts.GET("/active", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
}
// Pattern detection routes
patterns := v1.Group("/patterns")
{
patterns.GET("", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
patterns.POST("", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
patterns.PUT("/:id", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
patterns.DELETE("/:id", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
patterns.POST("/detect", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
patterns.POST("/ml/train", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
patterns.POST("/ml/detect", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
}
// Report routes
reports := v1.Group("/reports")
{
reports.GET("", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
reports.POST("", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
reports.GET("/:id", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
reports.PUT("/:id", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
reports.DELETE("/:id", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
reports.POST("/:id/execute", func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
})
}
}
return router
}
// corsMiddleware handles CORS headers
func corsMiddleware() gin.HandlerFunc {
return 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")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}

View File

@@ -0,0 +1,106 @@
package config
import (
"fmt"
"os"
"time"
"gopkg.in/yaml.v3"
)
type Config struct {
Server ServerConfig `yaml:"server"`
Database DatabaseConfig `yaml:"database"`
VictoriaLogs VictoriaLogsConfig `yaml:"victorialogs"`
VMAlert VMAlertConfig `yaml:"vmalert"`
MLService MLServiceConfig `yaml:"ml_service"`
Auth AuthConfig `yaml:"auth"`
RateLimiting RateLimitingConfig `yaml:"rate_limiting"`
}
type ServerConfig struct {
Port int `yaml:"port"`
ReadTimeout time.Duration `yaml:"read_timeout"`
WriteTimeout time.Duration `yaml:"write_timeout"`
}
type DatabaseConfig struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
Name string `yaml:"name"`
User string `yaml:"user"`
Password string `yaml:"password"`
MaxConnections int `yaml:"max_connections"`
}
type VictoriaLogsConfig struct {
URL string `yaml:"url"`
Timeout time.Duration `yaml:"timeout"`
MaxQuerySize int `yaml:"max_query_size"`
}
type VMAlertConfig struct {
URL string `yaml:"url"`
RulesDir string `yaml:"rules_dir"`
ReloadEndpoint string `yaml:"reload_endpoint"`
}
type MLServiceConfig struct {
URL string `yaml:"url"`
Timeout time.Duration `yaml:"timeout"`
}
type AuthConfig struct {
JWTSecret string `yaml:"jwt_secret"`
AccessTokenDuration time.Duration `yaml:"access_token_duration"`
RefreshTokenDuration time.Duration `yaml:"refresh_token_duration"`
}
type RateLimitingConfig struct {
Enabled bool `yaml:"enabled"`
RequestsPerMinute map[string]int `yaml:"requests_per_minute"`
}
// Load loads configuration from YAML file and environment variables
func Load(configPath string) (*Config, error) {
data, err := os.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("failed to read config file: %w", err)
}
// Expand environment variables in config
expandedData := []byte(os.ExpandEnv(string(data)))
var cfg Config
if err := yaml.Unmarshal(expandedData, &cfg); err != nil {
return nil, fmt.Errorf("failed to parse config: %w", err)
}
// Validate required fields
if err := cfg.validate(); err != nil {
return nil, fmt.Errorf("config validation failed: %w", err)
}
return &cfg, nil
}
func (c *Config) validate() error {
if c.Database.Password == "" {
return fmt.Errorf("database password is required")
}
if c.Auth.JWTSecret == "" {
return fmt.Errorf("JWT secret is required")
}
if len(c.Auth.JWTSecret) < 32 {
return fmt.Errorf("JWT secret must be at least 32 characters")
}
return nil
}
// GetDSN returns the PostgreSQL connection string
func (c *DatabaseConfig) GetDSN() string {
return fmt.Sprintf(
"host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
c.Host, c.Port, c.User, c.Password, c.Name,
)
}

View File

@@ -0,0 +1,78 @@
package models
import "time"
// Role represents user roles
type Role string
const (
RoleAdmin Role = "admin"
RoleEditor Role = "editor"
RoleAnalyst Role = "analyst"
RoleViewer Role = "viewer"
)
// Permission represents a specific permission
type Permission string
const (
PermViewLogs Permission = "logs:view"
PermExportLogs Permission = "logs:export"
PermManageAlerts Permission = "alerts:manage"
PermViewAlerts Permission = "alerts:view"
PermManagePatterns Permission = "patterns:manage"
PermRunML Permission = "patterns:ml"
PermManageReports Permission = "reports:manage"
PermManageUsers Permission = "users:manage"
)
// RolePermissions maps roles to their permissions
var RolePermissions = map[Role][]Permission{
RoleAdmin: {
PermViewLogs, PermExportLogs, PermManageAlerts,
PermViewAlerts, PermManagePatterns, PermRunML,
PermManageReports, PermManageUsers,
},
RoleEditor: {
PermViewLogs, PermExportLogs, PermManageAlerts,
PermViewAlerts, PermManagePatterns, PermManageReports,
},
RoleAnalyst: {
PermViewLogs, PermViewAlerts, PermManagePatterns, PermRunML,
},
RoleViewer: {
PermViewLogs, PermViewAlerts,
},
}
// User represents a user in the system
type User struct {
ID string `json:"id" db:"id"`
Username string `json:"username" db:"username"`
Email string `json:"email" db:"email"`
PasswordHash string `json:"-" db:"password_hash"`
Role Role `json:"role" db:"role"`
IsActive bool `json:"is_active" db:"is_active"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// HasPermission checks if the user has a specific permission
func (u *User) HasPermission(perm Permission) bool {
permissions, ok := RolePermissions[u.Role]
if !ok {
return false
}
for _, p := range permissions {
if p == perm {
return true
}
}
return false
}
// GetPermissions returns all permissions for the user's role
func (u *User) GetPermissions() []Permission {
return RolePermissions[u.Role]
}