Add cursor rules and initial configuration for Tender Management Go Backend

This commit is contained in:
n.nakhostin
2025-08-02 10:43:47 +03:30
parent ad9db7bcce
commit 5a1ceb0bd6
35 changed files with 4123 additions and 4550 deletions
-260
View File
@@ -1,260 +0,0 @@
package main
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/go-playground/validator/v10"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"tm/internal/domain"
"tm/internal/handler"
"tm/internal/infrastructure"
"tm/internal/repository"
"tm/internal/service"
"tm/pkg/logger"
customMiddleware "tm/pkg/middleware"
"tm/pkg/response"
)
func main() {
// Load configuration
config, err := infrastructure.LoadConfig("./configs")
if err != nil {
panic(fmt.Sprintf("Failed to load config: %v", err))
}
// Initialize logger
logger.InitLogger(&config.Logging)
log := logger.GetLogger()
log.Info("Starting Tender Management API Server", map[string]interface{}{
"version": "1.0.0",
"port": config.Server.Port,
})
// Initialize MongoDB connection
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(config.Database.MongoDB.URI))
if err != nil {
log.Fatal("Failed to connect to MongoDB", map[string]interface{}{
"error": err.Error(),
})
}
// Ping MongoDB to verify connection
if err = mongoClient.Ping(ctx, nil); err != nil {
log.Fatal("Failed to ping MongoDB", map[string]interface{}{
"error": err.Error(),
})
}
log.Info("Connected to MongoDB successfully", map[string]interface{}{
"database": config.Database.MongoDB.Name,
})
// Get MongoDB database
db := mongoClient.Database(config.Database.MongoDB.Name)
// Initialize validator
validate := validator.New()
// Initialize repositories
customerRepo := repository.NewCustomerRepository(db, log)
userRepo := repository.NewUserRepository(db, log)
companyRepo := repository.NewCompanyRepository(db, log)
// Initialize services
customerAuthService := service.NewCustomerAuthService(customerRepo, companyRepo, &config.Auth, log)
userAuthService := service.NewUserAuthService(userRepo, &config.Auth, log)
// TODO: Initialize customer service when needed for admin customer management
// customerService := service.NewCustomerService(customerRepo, log)
// Backward compatibility - create generic auth service
authService := handler.NewAuthHandler(customerAuthService, userAuthService, validate, log)
// Initialize handlers
customerAuthHandler := handler.NewCustomerAuthHandler(customerAuthService, validate, log)
userAuthHandler := handler.NewUserAuthHandler(userAuthService, validate, log)
// Initialize Echo
e := echo.New()
// Configure Echo
e.HideBanner = true
e.HidePort = true
// Add built-in middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.CORS())
e.Use(middleware.RequestID())
// Add custom middleware for logging
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
start := time.Now()
err := next(c)
log.Info("HTTP Request", map[string]interface{}{
"method": c.Request().Method,
"path": c.Request().URL.Path,
"status": c.Response().Status,
"duration": time.Since(start).String(),
"request_id": c.Response().Header().Get(echo.HeaderXRequestID),
})
return err
}
})
// Add authentication middleware
e.Use(customMiddleware.AuthMiddleware(customerAuthService, userAuthService, log))
// Setup routes
setupRoutes(e, authService, customerAuthHandler, userAuthHandler)
// Health check endpoint
e.GET("/health", func(c echo.Context) error {
return response.Success(c, map[string]interface{}{
"status": "healthy",
"timestamp": time.Now().UTC(),
"version": "1.0.0",
}, "Service is healthy")
})
// Start server in a goroutine
go func() {
address := fmt.Sprintf("%s:%d", config.Server.Host, config.Server.Port)
log.Info("Server starting", map[string]interface{}{
"address": address,
})
if err := e.Start(address); err != nil && err != http.ErrServerClosed {
log.Fatal("Failed to start server", map[string]interface{}{
"error": err.Error(),
})
}
}()
// Wait for interrupt signal to gracefully shutdown the server
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
<-quit
log.Info("Server is shutting down...", nil)
// Create a deadline for graceful shutdown
ctx, cancel = context.WithTimeout(context.Background(), config.Server.Timeout)
defer cancel()
// Gracefully shutdown the server
if err := e.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown", map[string]interface{}{
"error": err.Error(),
})
}
// Close MongoDB connection
if err := mongoClient.Disconnect(context.Background()); err != nil {
log.Error("Failed to disconnect from MongoDB", map[string]interface{}{
"error": err.Error(),
})
}
log.Info("Server stopped gracefully", nil)
// Cleanup logger to flush any remaining logs
logger.Cleanup()
}
// setupRoutes configures all API routes
func setupRoutes(
e *echo.Echo,
authHandler *handler.AuthHandler,
customerAuthHandler *handler.CustomerAuthHandler,
userAuthHandler *handler.UserAuthHandler,
) {
// Legacy/Generic authentication routes (backward compatibility)
auth := e.Group("/auth")
auth.POST("/register", authHandler.Register)
auth.POST("/login", authHandler.Login)
auth.POST("/refresh", authHandler.RefreshToken)
auth.POST("/change-password", authHandler.ChangePassword)
auth.GET("/profile", authHandler.GetProfile)
auth.POST("/logout", authHandler.Logout)
// API version group
api := e.Group("/api/v1")
// Mobile app routes (customers)
mobile := api.Group("/mobile")
mobileAuth := mobile.Group("/auth")
mobileAuth.POST("/register", customerAuthHandler.Register)
mobileAuth.POST("/login", customerAuthHandler.Login)
mobileAuth.POST("/refresh", customerAuthHandler.RefreshToken)
mobileAuth.POST("/verify-email", customerAuthHandler.VerifyEmail)
mobileAuth.POST("/resend-verification", customerAuthHandler.ResendVerification)
// Protected mobile routes
mobileAuth.POST("/change-password", customerAuthHandler.ChangePassword, customMiddleware.CustomerOnlyMiddleware())
mobileAuth.GET("/profile", customerAuthHandler.GetProfile, customMiddleware.CustomerOnlyMiddleware())
mobileAuth.POST("/logout", customerAuthHandler.Logout, customMiddleware.CustomerOnlyMiddleware())
// Mobile app tenders routes
mobileTenders := mobile.Group("/tenders", customMiddleware.CustomerOnlyMiddleware())
// TODO: Add tender routes for customers
_ = mobileTenders
// Mobile app companies routes
mobileCompanies := mobile.Group("/companies", customMiddleware.CustomerOnlyMiddleware())
// TODO: Add company routes for customers
_ = mobileCompanies
// Admin panel routes (panel users)
admin := api.Group("/admin")
adminAuth := admin.Group("/auth")
adminAuth.POST("/login", userAuthHandler.Login)
adminAuth.POST("/refresh", userAuthHandler.RefreshToken)
// Protected admin auth routes
adminAuth.POST("/change-password", userAuthHandler.ChangePassword, customMiddleware.UserOnlyMiddleware())
adminAuth.GET("/profile", userAuthHandler.GetProfile, customMiddleware.UserOnlyMiddleware())
adminAuth.POST("/logout", userAuthHandler.Logout, customMiddleware.UserOnlyMiddleware())
// Admin user management routes (admin only)
adminUsers := admin.Group("/users", customMiddleware.UserRoleMiddleware(domain.UserRoleAdmin))
adminUsers.POST("", userAuthHandler.CreatePanelUser)
adminUsers.PUT("/:user_id/permissions", userAuthHandler.UpdateUserPermissions)
// Admin customer management routes
adminCustomers := admin.Group("/customers", customMiddleware.UserRoleMiddleware(domain.UserRoleAdmin, domain.UserRoleModerator))
// TODO: Add customer management routes for admins
_ = adminCustomers
// Admin tender management routes
adminTenders := admin.Group("/tenders", customMiddleware.UserPermissionMiddleware("manage_tenders"))
// TODO: Add tender management routes for admins
_ = adminTenders
// Admin company management routes
adminCompanies := admin.Group("/companies", customMiddleware.UserPermissionMiddleware("manage_companies"))
// TODO: Add company management routes for admins
_ = adminCompanies
// Admin reports routes
adminReports := admin.Group("/reports", customMiddleware.UserPermissionMiddleware("view_reports"))
// TODO: Add reporting routes for admins
_ = adminReports
}
+33
View File
@@ -0,0 +1,33 @@
package main
import (
"fmt"
"tm/infra"
"tm/pkg/logger"
)
// Init Application Configuration
func intiConfig() infra.Config {
config, err := infra.LoadConfig("./")
if err != nil {
panic(fmt.Sprintf("Failed to load config: %v", err))
}
return *config
}
// Init Logger Service
func initLogger(conf infra.LoggingConfig) logger.Logger {
return logger.NewLogger(&logger.Config{
Level: conf.Level,
Format: conf.Format,
Output: conf.Output,
File: logger.FileConfig{
Path: conf.File.Path,
MaxSize: conf.File.MaxSize,
MaxBackups: conf.File.MaxBackups,
MaxAge: conf.File.MaxAge,
Compress: conf.File.Compress,
},
})
}
+70
View File
@@ -0,0 +1,70 @@
server:
host: "0.0.0.0"
port: 8080
timeout: 30s
read_timeout: 10s
write_timeout: 10s
database:
mongodb:
uri: "mongodb://localhost:27017"
name: "tender_management"
timeout: 10s
max_pool_size: 100
cache:
redis:
host: "localhost"
port: 6379
password: ""
db: 0
pool_size: 10
queue:
rabbitmq:
url: "amqp://guest:guest@localhost:5672/"
exchange: "tender_exchange"
queues:
scraping: "scraping_queue"
processing: "processing_queue"
notifications: "notifications_queue"
search:
elasticsearch:
urls: ["http://localhost:9200"]
username: ""
password: ""
index_prefix: "tender_"
auth:
jwt:
secret: "your-super-secret-key"
access_token_duration: "24h"
refresh_token_duration: "168h" # 7 days
ai:
openai:
api_key: ""
model: "gpt-3.5-turbo"
max_tokens: 1000
scraping:
user_agent: "TenderBot/1.0"
timeout: 30s
max_retries: 3
delay_between_requests: 1s
logging:
level: "info"
format: "json"
output: "file" # stdout, stderr, file
file:
path: "./logs/app.log"
max_size: 100 # Max size in MB before rotation
max_backups: 5 # Number of backup files to keep
max_age: 30 # Max age in days to keep log files
compress: true # Compress rotated files
rate_limiting:
requests_per_minute: 100
burst: 20
+16
View File
@@ -0,0 +1,16 @@
package main
func main() {
conf := intiConfig()
logger := initLogger(conf.Logging)
defer logger.Sync()
logger.Info(
"Starting Tender Management API Server",
map[string]interface{}{
"version": "1.0.0",
"host": conf.Server.Host,
"port": conf.Server.Port,
})
}