mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-18 11:02:54 +00:00
feat: Add REST API server with Gin framework and comprehensive eSIM profile management endpoints
- Add Gin router initialization with custom logging formatter and recovery middleware - Add setupRoutes function with /api/v1 prefix and health check endpoint - Add eSIM profile endpoints: POST /profiles for ordering, GET /profiles/:profileId for status, GET /profiles for listing with pagination, DELETE /profiles/:profileId for deletion - Add carrier management endpoints: GET /carrier/info for carrier
This commit is contained in:
+262
-26
@@ -3,9 +3,12 @@ package main
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/rs/zerolog"
|
||||
@@ -47,43 +50,84 @@ func main() {
|
||||
logger.Info().Msg("No .env file found, using system environment")
|
||||
}
|
||||
|
||||
logger.Info().Msg("Carrier Connector started")
|
||||
|
||||
// Initialize ES2+ client
|
||||
smdpURL := getEnv("SMDP_URL", "https://smdp.example.com")
|
||||
apiKey := getEnv("SMDP_API_KEY", "test-api-key")
|
||||
port := getEnv("PORT", "8080")
|
||||
|
||||
client := NewES2Client(smdpURL, apiKey)
|
||||
|
||||
// Example: Order a profile
|
||||
order := &ProfileOrder{
|
||||
ICCID: "8933123456789012345",
|
||||
IMSI: "208930000000001",
|
||||
K: "465B5CE8B199B49FAA5F0A2EE238A6BC",
|
||||
OPc: "E8ED289DEBA952E4283B54E88E6183CA",
|
||||
MCC: "208",
|
||||
MNC: "93",
|
||||
ProfileType: "operational",
|
||||
}
|
||||
// Initialize Gin router
|
||||
router := gin.Default()
|
||||
|
||||
// Add middleware for logging
|
||||
router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
|
||||
return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
|
||||
param.ClientIP,
|
||||
param.TimeStamp.Format(time.RFC1123),
|
||||
param.Method,
|
||||
param.Path,
|
||||
param.Request.Proto,
|
||||
param.StatusCode,
|
||||
param.Latency,
|
||||
param.Request.UserAgent(),
|
||||
param.ErrorMessage,
|
||||
)
|
||||
}))
|
||||
|
||||
// Add recovery middleware
|
||||
router.Use(gin.Recovery())
|
||||
|
||||
// Initialize API routes
|
||||
setupRoutes(router, client)
|
||||
|
||||
logger.Info().
|
||||
Str("imsi", order.IMSI).
|
||||
Msg("Ordering eSIM profile from SM-DP+")
|
||||
Str("port", port).
|
||||
Msg("Carrier Connector API server starting")
|
||||
|
||||
// In production, this would be triggered by API requests
|
||||
response, err := client.OrderProfile(order)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Failed to order profile")
|
||||
} else {
|
||||
logger.Info().
|
||||
Str("activation_code", response.ActivationCode).
|
||||
Str("profile_id", response.ProfileID).
|
||||
Msg("Profile ordered successfully")
|
||||
// Start server
|
||||
if err := router.Run(":" + port); err != nil {
|
||||
logger.Fatal().Err(err).Msg("Failed to start server")
|
||||
}
|
||||
}
|
||||
|
||||
func setupRoutes(router *gin.Engine, client *ES2Client) {
|
||||
api := router.Group("/api/v1")
|
||||
|
||||
// Health check endpoint
|
||||
api.GET("/health", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "healthy",
|
||||
"service": "carrier-connector",
|
||||
"timestamp": time.Now().UTC(),
|
||||
})
|
||||
})
|
||||
|
||||
// eSIM profile endpoints
|
||||
esim := api.Group("/esim")
|
||||
{
|
||||
// Order a new eSIM profile
|
||||
esim.POST("/profiles", orderProfileHandler(client))
|
||||
|
||||
// Get profile status
|
||||
esim.GET("/profiles/:profileId", getProfileHandler(client))
|
||||
|
||||
// List all profiles
|
||||
esim.GET("/profiles", listProfilesHandler(client))
|
||||
|
||||
// Delete/disable profile
|
||||
esim.DELETE("/profiles/:profileId", deleteProfileHandler(client))
|
||||
}
|
||||
|
||||
// Keep service running
|
||||
logger.Info().Msg("Carrier Connector running. Press Ctrl+C to exit.")
|
||||
select {}
|
||||
// Carrier management endpoints
|
||||
carrier := api.Group("/carrier")
|
||||
{
|
||||
// Get carrier info
|
||||
carrier.GET("/info", getCarrierInfoHandler(client))
|
||||
|
||||
// Check SM-DP+ connectivity
|
||||
carrier.GET("/connectivity", checkConnectivityHandler(client))
|
||||
}
|
||||
}
|
||||
|
||||
func NewES2Client(baseURL, apiKey string) *ES2Client {
|
||||
@@ -124,6 +168,198 @@ func (c *ES2Client) OrderProfile(order *ProfileOrder) (*ProfileResponse, error)
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// API Handlers
|
||||
|
||||
func orderProfileHandler(client *ES2Client) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var order ProfileOrder
|
||||
|
||||
if err := c.ShouldBindJSON(&order); err != nil {
|
||||
logger.Error().Err(err).Msg("Invalid request body")
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "Invalid request body",
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
if order.ICCID == "" || order.IMSI == "" || order.K == "" || order.OPc == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "Missing required fields",
|
||||
"message": "ICCID, IMSI, K, and OPc are required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info().
|
||||
Str("imsi", order.IMSI).
|
||||
Str("iccid", order.ICCID).
|
||||
Msg("API request: Ordering eSIM profile from SM-DP+")
|
||||
|
||||
response, err := client.OrderProfile(&order)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Str("imsi", order.IMSI).Msg("Failed to order profile")
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "Failed to order profile",
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info().
|
||||
Str("activation_code", response.ActivationCode).
|
||||
Str("profile_id", response.ProfileID).
|
||||
Str("imsi", order.IMSI).
|
||||
Msg("Profile ordered successfully via API")
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"profile": response,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getProfileHandler(client *ES2Client) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
profileID := c.Param("profileId")
|
||||
|
||||
if profileID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "Missing profile ID",
|
||||
"message": "Profile ID is required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info().
|
||||
Str("profile_id", profileID).
|
||||
Msg("API request: Getting eSIM profile status")
|
||||
|
||||
// TODO: Implement actual profile retrieval from SM-DP+
|
||||
// For now, return a mock response
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"profile": gin.H{
|
||||
"profileId": profileID,
|
||||
"status": "active",
|
||||
"createdAt": time.Now().UTC(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func listProfilesHandler(client *ES2Client) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
logger.Info().Msg("API request: Listing eSIM profiles")
|
||||
|
||||
// Parse query parameters
|
||||
page := 1
|
||||
if pageStr := c.Query("page"); pageStr != "" {
|
||||
if parsed, err := strconv.Atoi(pageStr); err == nil && parsed > 0 {
|
||||
page = parsed
|
||||
}
|
||||
}
|
||||
|
||||
limit := 20
|
||||
if limitStr := c.Query("limit"); limitStr != "" {
|
||||
if parsed, err := strconv.Atoi(limitStr); err == nil && parsed > 0 && parsed <= 100 {
|
||||
limit = parsed
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Implement actual profile listing from database/SM-DP+
|
||||
// For now, return a mock response
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"profiles": []gin.H{
|
||||
{
|
||||
"profileId": "profile-123",
|
||||
"imsi": "208930000000001",
|
||||
"status": "active",
|
||||
},
|
||||
{
|
||||
"profileId": "profile-456",
|
||||
"imsi": "208930000000002",
|
||||
"status": "inactive",
|
||||
},
|
||||
},
|
||||
"pagination": gin.H{
|
||||
"page": page,
|
||||
"limit": limit,
|
||||
"total": 2,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func deleteProfileHandler(client *ES2Client) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
profileID := c.Param("profileId")
|
||||
|
||||
if profileID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "Missing profile ID",
|
||||
"message": "Profile ID is required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info().
|
||||
Str("profile_id", profileID).
|
||||
Msg("API request: Deleting eSIM profile")
|
||||
|
||||
// TODO: Implement actual profile deletion/disable from SM-DP+
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "Profile deleted successfully",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getCarrierInfoHandler(client *ES2Client) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
logger.Info().Msg("API request: Getting carrier information")
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"carrier": gin.H{
|
||||
"name": "Example Carrier",
|
||||
"mcc": "208",
|
||||
"mnc": "93",
|
||||
"smdpUrl": client.baseURL,
|
||||
"supported": true,
|
||||
"lastChecked": time.Now().UTC(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func checkConnectivityHandler(client *ES2Client) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
logger.Info().Msg("API request: Checking SM-DP+ connectivity")
|
||||
|
||||
// Simple connectivity check by making a request to SM-DP+
|
||||
resp, err := client.httpClient.R().Get("/health")
|
||||
|
||||
isConnected := err == nil && resp.StatusCode() < 500
|
||||
statusCode := 0
|
||||
if resp != nil {
|
||||
statusCode = resp.StatusCode()
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"connectivity": gin.H{
|
||||
"smdpConnected": isConnected,
|
||||
"statusCode": statusCode,
|
||||
"checkedAt": time.Now().UTC(),
|
||||
"error": nil,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getEnv(key, fallback string) string {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
return value
|
||||
|
||||
Reference in New Issue
Block a user