mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-28 07:32:15 +00:00
- Add OrderProfileHandlerWithRepo with ICCID/IMSI/K/OPc validation, ES2+ DownloadProfile call, and Profile creation with ICCID/EID/IMSI/MCC/MNC/ProfileType/State/TenantID persistence - Add GetProfileHandlerWithRepo with repository.Get/ES2+ GetProfileStatus calls and merged response with state/imsi/tenantId/createdAt/updatedAt/executionStatus/profileState/statusMessage fields - Add ListProfilesHandlerWithRepo with pagination support, page/limit query
198 lines
6.2 KiB
Go
198 lines
6.2 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/es2"
|
|
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository"
|
|
)
|
|
|
|
// OrderProfileHandlerWithRepo orders a profile via ES2+ and persists it in the repo.
|
|
func OrderProfileHandlerWithRepo(client *es2.ES2Client, repo repository.ProfileRepository) 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
|
|
}
|
|
|
|
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("Ordering eSIM profile from SM-DP+")
|
|
|
|
downloadResp, err := client.DownloadProfile(context.Background(), &es2.DownloadProfileRequest{
|
|
EID: order.EID,
|
|
ICCID: order.ICCID,
|
|
ProfileType: order.ProfileType,
|
|
ConfirmationCode: order.ConfirmationCode,
|
|
})
|
|
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
|
|
}
|
|
|
|
profile := &repository.Profile{
|
|
ICCID: order.ICCID,
|
|
EID: order.EID,
|
|
IMSI: order.IMSI,
|
|
MCC: order.MCC,
|
|
MNC: order.MNC,
|
|
ProfileType: order.ProfileType,
|
|
State: "provisioned",
|
|
TenantID: c.GetHeader("X-Tenant-ID"),
|
|
}
|
|
if err := repo.Create(c.Request.Context(), profile); err != nil {
|
|
Logger.Warn().Err(err).Str("iccid", order.ICCID).Msg("Profile repo write failed")
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"profile": &ProfileResponse{
|
|
ExecutionStatus: downloadResp.ExecutionStatus,
|
|
StatusMessage: downloadResp.StatusMessage,
|
|
ProfileID: order.ICCID,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
// GetProfileHandlerWithRepo returns the stored profile enriched with live ES2+ status.
|
|
func GetProfileHandlerWithRepo(client *es2.ES2Client, repo repository.ProfileRepository) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
profileID := c.Param("profileId")
|
|
if profileID == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Missing profile ID"})
|
|
return
|
|
}
|
|
|
|
stored, err := repo.Get(c.Request.Context(), profileID)
|
|
if err != nil && !errors.Is(err, repository.ErrNotFound) {
|
|
Logger.Error().Err(err).Str("profile_id", profileID).Msg("Failed to read profile from store")
|
|
}
|
|
|
|
statusResp, statusErr := client.GetProfileStatus(context.Background(), &es2.GetProfileStatusRequest{
|
|
ICCID: profileID,
|
|
EID: safeEID(stored),
|
|
})
|
|
|
|
payload := gin.H{
|
|
"profileId": profileID,
|
|
"checkedAt": time.Now().UTC(),
|
|
}
|
|
if stored != nil {
|
|
payload["state"] = stored.State
|
|
payload["imsi"] = stored.IMSI
|
|
payload["tenantId"] = stored.TenantID
|
|
payload["createdAt"] = stored.CreatedAt
|
|
payload["updatedAt"] = stored.UpdatedAt
|
|
}
|
|
if statusErr != nil {
|
|
Logger.Warn().Err(statusErr).Str("profile_id", profileID).Msg("Live profile status unavailable")
|
|
payload["liveStatusError"] = statusErr.Error()
|
|
} else {
|
|
payload["executionStatus"] = statusResp.ExecutionStatus
|
|
payload["profileState"] = statusResp.ProfileState
|
|
payload["statusMessage"] = statusResp.StatusMessage
|
|
}
|
|
|
|
if stored == nil && statusErr != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Profile not found", "profileId": profileID})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"success": true, "profile": payload})
|
|
}
|
|
}
|
|
|
|
// ListProfilesHandlerWithRepo returns a paginated list of stored profiles.
|
|
func ListProfilesHandlerWithRepo(repo repository.ProfileRepository) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
page := parsePositiveInt(c.Query("page"), 1)
|
|
limit := parsePositiveInt(c.Query("limit"), 20)
|
|
if limit > 100 {
|
|
limit = 100
|
|
}
|
|
|
|
Logger.Info().Int("page", page).Int("limit", limit).Msg("Listing eSIM profiles")
|
|
|
|
profiles, total, err := repo.List(c.Request.Context(), repository.ListFilter{
|
|
TenantID: c.Query("tenantId"),
|
|
State: c.Query("state"),
|
|
Limit: limit,
|
|
Offset: (page - 1) * limit,
|
|
})
|
|
if err != nil {
|
|
Logger.Error().Err(err).Msg("Failed to list profiles")
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list profiles", "message": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"profiles": profiles,
|
|
"pagination": gin.H{
|
|
"page": page,
|
|
"limit": limit,
|
|
"total": total,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
// DeleteProfileHandlerWithRepo deletes a profile from the SM-DP+ and marks it deleted in the repo.
|
|
func DeleteProfileHandlerWithRepo(client *es2.ES2Client, repo repository.ProfileRepository) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
profileID := c.Param("profileId")
|
|
if profileID == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Missing profile ID"})
|
|
return
|
|
}
|
|
|
|
deleteResp, err := client.DeleteProfile(context.Background(), &es2.DeleteProfileRequest{ICCID: profileID})
|
|
if err != nil {
|
|
Logger.Error().Err(err).Str("profile_id", profileID).Msg("Failed to delete profile upstream")
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete profile", "message": err.Error()})
|
|
return
|
|
}
|
|
|
|
if _, err := repo.UpdateState(c.Request.Context(), profileID, "deleted"); err != nil && !errors.Is(err, repository.ErrNotFound) {
|
|
Logger.Warn().Err(err).Str("profile_id", profileID).Msg("Profile repo update failed")
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "Profile deletion requested",
|
|
"executionStatus": deleteResp.ExecutionStatus,
|
|
"statusMessage": deleteResp.StatusMessage,
|
|
})
|
|
}
|
|
}
|
|
|
|
// safeEID returns the EID from a stored profile or empty string if nil.
|
|
func safeEID(p *repository.Profile) string {
|
|
if p == nil {
|
|
return ""
|
|
}
|
|
return p.EID
|
|
}
|
|
|
|
// unused import guard (strconv used indirectly by parsePositiveInt in handlers.go)
|
|
var _ = strconv.Atoi
|