Files
Authentication/main.go
T
2026-01-06 09:11:28 +08:00

252 lines
7.2 KiB
Go

package main
import (
"authentication/db"
"authentication/docs"
"authentication/helper"
"authentication/models"
"authentication/redisclient"
"database/sql"
"fmt"
"log"
"net"
"authentication/routes"
"net/http"
"os"
"time"
"github.com/getsentry/sentry-go"
"github.com/gorilla/mux"
"github.com/joho/godotenv"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/rs/cors"
)
// @swagger: "2.0"
// @title UESS Authentication Microservice
// @version 1.0
// @description This is the API for Authentication Microservice for UESS. It doesn't support OAS 3.0 and is only for documentation purposes. The library used doesn't support @server annotation.
// @contact.name Darrel Israel
// @contact.email d.israel.psa@gmail.com
// @BasePath /
// @securityDefinitions.apikey BearerToken
// @in header
// @name Authorization
var (
dbOpenConnections = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "db_open_connections",
Help: "Number of open database connections",
})
dbInUseConnections = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "db_in_use_connections",
Help: "Number of in-use database connections",
})
dbIdleConnections = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "db_idle_connections",
Help: "Number of idle database connections",
})
)
var (
httpRequestsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"path", "method"},
)
httpRequestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "Duration of HTTP requests in seconds",
Buckets: prometheus.DefBuckets,
},
[]string{"path", "method"},
)
httpRequestSize = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_size_bytes",
Help: "Size of HTTP requests in bytes",
Buckets: prometheus.ExponentialBuckets(100, 10, 8),
},
[]string{"path", "method"},
)
httpResponseSize = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_response_size_bytes",
Help: "Size of HTTP responses in bytes",
Buckets: prometheus.ExponentialBuckets(100, 10, 8),
},
[]string{"path", "method"},
)
)
func init() {
prometheus.MustRegister(httpRequestsTotal)
prometheus.MustRegister(httpRequestDuration)
prometheus.MustRegister(httpRequestSize)
prometheus.MustRegister(httpResponseSize)
prometheus.MustRegister(dbOpenConnections)
prometheus.MustRegister(dbInUseConnections)
prometheus.MustRegister(dbIdleConnections)
}
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
metricsPath := os.Getenv("METRICS_PATH")
if r.URL.Path != metricsPath {
helper.LogInfo(fmt.Sprintf("INFO: Started %s %s", r.Method, r.URL.Path))
}
httpRequestsTotal.WithLabelValues(r.URL.Path, r.Method).Inc()
requestSize := float64(r.ContentLength)
if requestSize < 0 {
requestSize = 0
}
httpRequestSize.WithLabelValues(r.URL.Path, r.Method).Observe(requestSize)
rw := &models.ResponseWriter{ResponseWriter: w}
next.ServeHTTP(rw, r)
duration := time.Since(start).Seconds()
httpRequestDuration.WithLabelValues(r.URL.Path, r.Method).Observe(duration)
httpResponseSize.WithLabelValues(r.URL.Path, r.Method).Observe(float64(rw.Size))
// Log completion for non-metrics endpoints
if r.URL.Path != metricsPath {
helper.LogInfo(fmt.Sprintf("INFO: Completed %s %s in %.3f seconds", r.Method, r.URL.Path, duration))
}
})
}
func collectDBMetrics(database *sql.DB) {
for {
stats := database.Stats()
dbOpenConnections.Set(float64(stats.OpenConnections))
dbInUseConnections.Set(float64(stats.InUse))
dbIdleConnections.Set(float64(stats.Idle))
time.Sleep(10 * time.Second) // Adjust the interval as needed
}
}
func allowOnlyGrafana(next http.Handler, allowedIP string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
remoteIP, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
if remoteIP == allowedIP {
next.ServeHTTP(w, r)
return
}
http.Error(w, "Forbidden", http.StatusForbidden)
})
}
func main() {
// Load environment variables from .env file first
// Get current working directory for debugging
cwd, _ := os.Getwd()
metricsPath := os.Getenv("METRICS_PATH")
if metricsPath == "" {
log.Print("METRICS_PATH not set, defaulting to /metrics")
metricsPath = "/metrics"
}
log.Printf("Current working directory: %s", cwd)
err := godotenv.Load()
if err != nil {
log.Printf("ERROR: Failed to load .env file from default location: %v", err)
// Try with explicit path
err = godotenv.Load(".env")
if err != nil {
log.Fatalf("FATAL: Could not load .env file: %v. Tried paths: default and ./.env", err)
}
log.Println(".env file loaded successfully from ./.env")
} else {
log.Println(".env file loaded successfully")
}
// Verify GO_ENV is loaded
goEnv := os.Getenv("GO_ENV")
log.Printf("GO_ENV value after loading .env: '%s'", goEnv)
if goEnv == "" {
log.Fatal("GO_ENV is not set in main. Please set the GO_ENV environment variable.")
}
DSN := os.Getenv("DSN")
if DSN == "" {
log.Fatal("Sentry DSN is not set. Please set the DSN environment variable.")
}
err = sentry.Init(sentry.ClientOptions{
Dsn: os.Getenv("DSN"),
TracesSampleRate: 1.0,
Environment: goEnv,
})
if err != nil {
log.Fatalf("sentry.Init: %s", err)
}
defer sentry.Flush(2 * time.Second)
docs.SwaggerInfo.Host = "localhost:8080"
docs.SwaggerInfo.Schemes = []string{"http"}
helper.LogInfo("INFO: Initializing database connection...")
var database *sql.DB
for {
database, err = db.InitDB()
if err == nil {
break
}
helper.LogError(fmt.Errorf("ERROR: error initializing database: %v", err), "database initialization error")
time.Sleep(2 * time.Second)
}
go collectDBMetrics(database)
router := mux.NewRouter()
routes.SetupRoutes(router, database)
helper.LogInfo("INFO: Database initialized successfully.")
allowedIP := os.Getenv("ALLOWED_IP")
helper.LogInfo("INFO: Setting up routes...")
router.Handle(metricsPath, allowOnlyGrafana(promhttp.Handler(), allowedIP))
router.Use(loggingMiddleware)
c := cors.New(cors.Options{
AllowedOrigins: []string{"http://localhost:4173", "http://localhost:5173"}, // Your frontend URL
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"},
AllowedHeaders: []string{"*"}, // Allow all headers temporarily
AllowCredentials: true, // Critical for withCredentials requests
MaxAge: 86400, // Cache preflight results
})
handler := c.Handler(router)
redisclient.Init()
helper.LogInfo("INFO: Connected to Redis successfully!")
helper.LogInfo("WARNING: Ensure Redis is secured to prevent unauthorized access. Use a strong password and bind Redis to localhost or a secure network.")
helper.LogInfo("INFO: Authentication Microservice is running on http://localhost:8081")
server := &http.Server{
Addr: ":8081",
Handler: handler,
ReadTimeout: 15 * time.Second,
WriteTimeout: 300 * time.Second,
IdleTimeout: 60 * time.Second,
}
log.Fatal(server.ListenAndServe())
}