Add configuration file and update server settings

- Introduced a new `config.yaml` file for managing server, database, cache, queue, AI, scraping, logging, and rate limiting configurations.
- Updated `docker-compose.yml` to reflect the new server port (8081) and adjusted health check endpoints accordingly.
- Modified `Dockerfile` to expose the new server port.
- Updated `README.md` to reflect changes in server configuration and added documentation for the new configuration structure.
- Added test scripts for server and Swagger documentation testing.
- Refactored customer domain structure to align with new configuration settings and improve maintainability.
This commit is contained in:
n.nakhostin
2025-08-02 12:37:04 +03:30
parent 06dc1d5b7a
commit 9119e2383e
30 changed files with 7273 additions and 1106 deletions
+102 -2
View File
@@ -2,13 +2,20 @@ package main
import (
"fmt"
"net/http"
"time"
"tm/infra"
"tm/pkg/logger"
"tm/pkg/mongo"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
echoSwagger "github.com/swaggo/echo-swagger"
)
// Init Application Configuration
func intiConfig() infra.Config {
config, err := infra.LoadConfig("./")
func initConfig() infra.Config {
config, err := infra.LoadConfig(".")
if err != nil {
panic(fmt.Sprintf("Failed to load config: %v", err))
}
@@ -31,3 +38,96 @@ func initLogger(conf infra.LoggingConfig) logger.Logger {
},
})
}
// Init MongoDB Connection Manager
func initMongoDB(conf infra.MongoConfig, log logger.Logger) *mongo.ConnectionManager {
// Convert infra.MongoConfig to mongo.ConnectionConfig
connectionConfig := mongo.ConnectionConfig{
URI: conf.URI,
Database: conf.Name,
MaxPoolSize: uint64(conf.MaxPoolSize),
MinPoolSize: 5, // Default minimum pool size
MaxConnIdleTime: 30 * time.Minute, // Default idle time
ConnectTimeout: conf.Timeout,
ServerSelectionTimeout: 5 * time.Second, // Default server selection timeout
}
// Create MongoDB connection manager
connectionManager, err := mongo.NewConnectionManager(connectionConfig, log)
if err != nil {
log.Error("Failed to initialize MongoDB connection", map[string]interface{}{
"error": err.Error(),
"uri": conf.URI,
"db": conf.Name,
})
panic(fmt.Sprintf("Failed to initialize MongoDB connection: %v", err))
}
log.Info("MongoDB connection manager initialized successfully", map[string]interface{}{
"database": conf.Name,
"uri": conf.URI,
"pool_size": conf.MaxPoolSize,
})
return connectionManager
}
// initHTTPServer initializes the Echo HTTP server with middleware
func initHTTPServer(conf infra.Config, log logger.Logger) *echo.Echo {
e := echo.New()
// Configure Echo
// e.HideBanner = true
// e.HidePort = true
// Add middleware
e.Use(middleware.Recover())
e.Use(middleware.RequestID())
e.Use(middleware.Logger())
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"*"}, // TODO: Configure properly for production
AllowMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodOptions},
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, echo.HeaderAuthorization},
}))
// Add custom middleware for logging
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
start := time.Now()
// Process request
err := next(c)
// Log request details
duration := time.Since(start)
log.Info("HTTP request processed", map[string]interface{}{
"method": c.Request().Method,
"path": c.Request().URL.Path,
"status": c.Response().Status,
"duration": duration.String(),
"user_agent": c.Request().UserAgent(),
"remote_ip": c.RealIP(),
})
return err
}
})
// Add health check endpoint
e.GET("/health", func(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]interface{}{
"status": "healthy",
"time": time.Now().Unix(),
"version": "1.0.0",
})
})
// Add Swagger documentation endpoint
e.GET("/swagger/*", echoSwagger.WrapHandler)
log.Info("HTTP server initialized successfully", map[string]interface{}{
"middleware": []string{"recover", "request_id", "logger", "cors", "custom_logging"},
})
return e
}
+1 -1
View File
@@ -1,6 +1,6 @@
server:
host: "0.0.0.0"
port: 8080
port: 8081
timeout: 30s
read_timeout: 10s
write_timeout: 10s
+1173
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+719
View File
@@ -0,0 +1,719 @@
basePath: /api/v1
definitions:
customer.AddDeviceTokenForm:
properties:
device_token:
type: string
device_type:
type: string
type: object
customer.AuthResponse:
properties:
access_token:
type: string
customer:
$ref: '#/definitions/customer.CustomerResponse'
expires_at:
type: integer
refresh_token:
type: string
type: object
customer.ChangePasswordForm:
properties:
new_password:
type: string
old_password:
type: string
type: object
customer.CustomerResponse:
properties:
birthdate:
type: integer
company_id:
type: string
created_at:
type: integer
device_tokens:
items:
$ref: '#/definitions/customer.DeviceToken'
type: array
email:
type: string
full_name:
type: string
gender:
type: string
id:
type: string
is_verified:
type: boolean
last_login_at:
type: integer
mobile:
type: string
national_id:
type: string
profile_image:
type: string
status:
type: string
updated_at:
type: integer
username:
type: string
type: object
customer.DeviceToken:
properties:
createdAt:
description: Unix timestamp
type: integer
deviceType:
$ref: '#/definitions/customer.DeviceType'
token:
type: string
updatedAt:
description: Unix timestamp
type: integer
type: object
customer.DeviceType:
enum:
- android
- ios
type: string
x-enum-varnames:
- DeviceTypeAndroid
- DeviceTypeIOS
customer.LoginForm:
properties:
email_or_mobile:
type: string
password:
type: string
type: object
customer.RefreshTokenForm:
properties:
refresh_token:
type: string
type: object
customer.RegisterCustomerForm:
properties:
birthdate:
type: integer
company_id:
type: string
email:
type: string
full_name:
type: string
gender:
type: string
mobile:
type: string
national_id:
type: string
password:
type: string
profile_image:
type: string
username:
type: string
type: object
customer.RemoveDeviceTokenForm:
properties:
device_token:
type: string
type: object
customer.UpdateCustomerForm:
properties:
birthdate:
type: integer
email:
type: string
full_name:
type: string
gender:
type: string
mobile:
type: string
national_id:
type: string
profile_image:
type: string
status:
type: string
username:
type: string
type: object
customer.UpdateCustomerStatusForm:
properties:
status:
type: string
type: object
response.APIError:
properties:
code:
type: string
details:
type: string
message:
type: string
type: object
response.APIResponse:
properties:
data: {}
error:
$ref: '#/definitions/response.APIError'
message:
type: string
meta:
$ref: '#/definitions/response.Meta'
success:
type: boolean
type: object
response.Meta:
properties:
limit:
type: integer
offset:
type: integer
page:
type: integer
pages:
type: integer
total:
type: integer
type: object
host: localhost:8081
info:
contact:
email: support@swagger.io
name: API Support
url: http://www.swagger.io/support
description: This is the API documentation for the Tender Management System.
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
termsOfService: http://swagger.io/terms/
title: Tender Management API
version: 1.0.0
paths:
/admin/customers:
get:
consumes:
- application/json
description: Retrieve a paginated list of customers (admin only)
parameters:
- default: 1
description: Page number
in: query
name: page
type: integer
- default: 20
description: Number of items per page
in: query
name: limit
type: integer
- description: Search term
in: query
name: search
type: string
produces:
- application/json
responses:
"200":
description: Customers retrieved successfully
schema:
allOf:
- $ref: '#/definitions/response.APIResponse'
- properties:
data:
items:
$ref: '#/definitions/customer.CustomerResponse'
type: array
type: object
"400":
description: Bad request
schema:
$ref: '#/definitions/response.APIResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal server error
schema:
$ref: '#/definitions/response.APIResponse'
security:
- BearerAuth: []
summary: List customers
tags:
- customers
/admin/customers/{id}:
get:
consumes:
- application/json
description: Retrieve customer information by ID (admin only)
parameters:
- description: Customer ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: Customer retrieved successfully
schema:
allOf:
- $ref: '#/definitions/response.APIResponse'
- properties:
data:
$ref: '#/definitions/customer.CustomerResponse'
type: object
"400":
description: Bad request
schema:
$ref: '#/definitions/response.APIResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.APIResponse'
"404":
description: Customer not found
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal server error
schema:
$ref: '#/definitions/response.APIResponse'
security:
- BearerAuth: []
summary: Get customer by ID
tags:
- customers
/admin/customers/{id}/status:
put:
consumes:
- application/json
description: Update customer status (admin only)
parameters:
- description: Customer ID
in: path
name: id
required: true
type: string
- description: Status update information
in: body
name: status
required: true
schema:
$ref: '#/definitions/customer.UpdateCustomerStatusForm'
produces:
- application/json
responses:
"200":
description: Customer status updated successfully
schema:
$ref: '#/definitions/response.APIResponse'
"400":
description: Bad request
schema:
$ref: '#/definitions/response.APIResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.APIResponse'
"404":
description: Customer not found
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal server error
schema:
$ref: '#/definitions/response.APIResponse'
security:
- BearerAuth: []
summary: Update customer status
tags:
- customers
/admin/customers/device-tokens:
get:
consumes:
- application/json
description: Retrieve all device tokens (admin only)
produces:
- application/json
responses:
"200":
description: Device tokens retrieved successfully
schema:
allOf:
- $ref: '#/definitions/response.APIResponse'
- properties:
data:
items:
type: string
type: array
type: object
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal server error
schema:
$ref: '#/definitions/response.APIResponse'
security:
- BearerAuth: []
summary: Get all device tokens
tags:
- customers
/admin/customers/register:
post:
consumes:
- application/json
description: Register a new customer with the provided information
parameters:
- description: Customer registration information
in: body
name: customer
required: true
schema:
$ref: '#/definitions/customer.RegisterCustomerForm'
produces:
- application/json
responses:
"201":
description: Customer registered successfully
schema:
allOf:
- $ref: '#/definitions/response.APIResponse'
- properties:
data:
$ref: '#/definitions/customer.CustomerResponse'
type: object
"400":
description: Bad request
schema:
$ref: '#/definitions/response.APIResponse'
"409":
description: Customer already exists
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal server error
schema:
$ref: '#/definitions/response.APIResponse'
summary: Register a new customer
tags:
- customers
/customers/change-password:
put:
consumes:
- application/json
description: Change the authenticated customer's password
parameters:
- description: Password change information
in: body
name: password
required: true
schema:
$ref: '#/definitions/customer.ChangePasswordForm'
produces:
- application/json
responses:
"200":
description: Password changed successfully
schema:
$ref: '#/definitions/response.APIResponse'
"400":
description: Bad request
schema:
$ref: '#/definitions/response.APIResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal server error
schema:
$ref: '#/definitions/response.APIResponse'
security:
- BearerAuth: []
summary: Change customer password
tags:
- customers
/customers/device-token:
delete:
consumes:
- application/json
description: Remove a device token for push notifications
parameters:
- description: Device token information
in: body
name: device_token
required: true
schema:
$ref: '#/definitions/customer.RemoveDeviceTokenForm'
produces:
- application/json
responses:
"200":
description: Device token removed successfully
schema:
$ref: '#/definitions/response.APIResponse'
"400":
description: Bad request
schema:
$ref: '#/definitions/response.APIResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal server error
schema:
$ref: '#/definitions/response.APIResponse'
security:
- BearerAuth: []
summary: Remove device token
tags:
- customers
post:
consumes:
- application/json
description: Add a device token for push notifications
parameters:
- description: Device token information
in: body
name: device_token
required: true
schema:
$ref: '#/definitions/customer.AddDeviceTokenForm'
produces:
- application/json
responses:
"200":
description: Device token added successfully
schema:
$ref: '#/definitions/response.APIResponse'
"400":
description: Bad request
schema:
$ref: '#/definitions/response.APIResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal server error
schema:
$ref: '#/definitions/response.APIResponse'
security:
- BearerAuth: []
summary: Add device token
tags:
- customers
/customers/login:
post:
consumes:
- application/json
description: Authenticate customer with email and password
parameters:
- description: Login credentials
in: body
name: credentials
required: true
schema:
$ref: '#/definitions/customer.LoginForm'
produces:
- application/json
responses:
"200":
description: Login successful
schema:
allOf:
- $ref: '#/definitions/response.APIResponse'
- properties:
data:
$ref: '#/definitions/customer.AuthResponse'
type: object
"400":
description: Bad request
schema:
$ref: '#/definitions/response.APIResponse'
"401":
description: Invalid credentials
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal server error
schema:
$ref: '#/definitions/response.APIResponse'
summary: Customer login
tags:
- customers
/customers/logout:
post:
consumes:
- application/json
description: Logout customer and invalidate device token
parameters:
- description: Logout request with device token
in: body
name: logout
required: true
schema:
type: object
produces:
- application/json
responses:
"200":
description: Logged out successfully
schema:
$ref: '#/definitions/response.APIResponse'
"400":
description: Bad request
schema:
$ref: '#/definitions/response.APIResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal server error
schema:
$ref: '#/definitions/response.APIResponse'
security:
- BearerAuth: []
summary: Customer logout
tags:
- customers
/customers/profile:
get:
consumes:
- application/json
description: Retrieve the authenticated customer's profile information
produces:
- application/json
responses:
"200":
description: Profile retrieved successfully
schema:
allOf:
- $ref: '#/definitions/response.APIResponse'
- properties:
data:
$ref: '#/definitions/customer.CustomerResponse'
type: object
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.APIResponse'
"404":
description: Customer not found
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal server error
schema:
$ref: '#/definitions/response.APIResponse'
security:
- BearerAuth: []
summary: Get customer profile
tags:
- customers
put:
consumes:
- application/json
description: Update the authenticated customer's profile information
parameters:
- description: Profile update information
in: body
name: profile
required: true
schema:
$ref: '#/definitions/customer.UpdateCustomerForm'
produces:
- application/json
responses:
"200":
description: Profile updated successfully
schema:
allOf:
- $ref: '#/definitions/response.APIResponse'
- properties:
data:
$ref: '#/definitions/customer.CustomerResponse'
type: object
"400":
description: Bad request
schema:
$ref: '#/definitions/response.APIResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal server error
schema:
$ref: '#/definitions/response.APIResponse'
security:
- BearerAuth: []
summary: Update customer profile
tags:
- customers
/customers/refresh-token:
post:
consumes:
- application/json
description: Refresh the access token using a refresh token
parameters:
- description: Refresh token information
in: body
name: token
required: true
schema:
$ref: '#/definitions/customer.RefreshTokenForm'
produces:
- application/json
responses:
"200":
description: Token refreshed successfully
schema:
allOf:
- $ref: '#/definitions/response.APIResponse'
- properties:
data:
$ref: '#/definitions/customer.AuthResponse'
type: object
"400":
description: Bad request
schema:
$ref: '#/definitions/response.APIResponse'
"401":
description: Invalid refresh token
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal server error
schema:
$ref: '#/definitions/response.APIResponse'
summary: Refresh access token
tags:
- customers
securityDefinitions:
BearerAuth:
description: Type "Bearer" followed by a space and JWT token.
in: header
name: Authorization
type: apiKey
swagger: "2.0"
tags:
- description: Customer management operations
name: customers
- description: Health check operations
name: health
- description: Authentication operations
name: auth
+112 -1
View File
@@ -1,11 +1,81 @@
// Package main Tender Management API
//
// This is the API documentation for the Tender Management System.
//
// Schemes: http, https
// Host: localhost:8081
// BasePath: /api/v1
// Version: 1.0.0
//
// Consumes:
// - application/json
//
// Produces:
// - application/json
//
// Security:
// - bearer
//
// swagger:meta
package main
// @title Tender Management API
// @version 1.0.0
// @description This is the API documentation for the Tender Management System.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8081
// @BasePath /api/v1
// @securityDefinitions.apikey BearerAuth
// @in header
// @name Authorization
// @description Type "Bearer" followed by a space and JWT token.
// @tag.name customers
// @tag.description Customer management operations
// @tag.name health
// @tag.description Health check operations
// @tag.name auth
// @tag.description Authentication operations
import (
"context"
"fmt"
"net/http"
"time"
"tm/internal/customer"
_ "tm/cmd/web/docs" // This is generated by swag
)
func main() {
conf := intiConfig()
conf := initConfig()
logger := initLogger(conf.Logging)
defer logger.Sync()
// Initialize MongoDB connection manager
mongoManager := initMongoDB(conf.Database.MongoDB, logger)
defer func() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := mongoManager.Close(ctx); err != nil {
logger.Error("Failed to close MongoDB connection", map[string]interface{}{
"error": err.Error(),
})
}
}()
logger.Info(
"Starting Tender Management API Server",
map[string]interface{}{
@@ -13,4 +83,45 @@ func main() {
"host": conf.Server.Host,
"port": conf.Server.Port,
})
// Initialize repositories with MongoDB connection manager
customerRepository := customer.NewCustomerRepository(mongoManager, logger)
logger.Info("Repositories initialized successfully", map[string]interface{}{
"repositories": []string{"customer"},
})
// Initialize services with repositories
customerService := customer.NewCustomerService(customerRepository, logger)
logger.Info("Services initialized successfully", map[string]interface{}{
"services": []string{"customer"},
})
// Initialize handlers with services
customerHandler := customer.NewHandler(customerService)
logger.Info("Handlers initialized successfully", map[string]interface{}{
"handlers": []string{"customer"},
})
// Initialize HTTP server
e := initHTTPServer(conf, logger)
// Register routes
customerHandler.RegisterRoutes(e)
// Start server
serverAddr := fmt.Sprintf("%s:%d", conf.Server.Host, conf.Server.Port)
logger.Info("HTTP server starting", map[string]interface{}{
"address": serverAddr,
})
if err := e.Start(serverAddr); err != nil && err != http.ErrServerClosed {
logger.Error("Failed to start HTTP server", map[string]interface{}{
"error": err.Error(),
"address": serverAddr,
})
panic(fmt.Sprintf("Failed to start HTTP server: %v", err))
}
}