diff --git a/apps/api-server/internal/services/es2_client.go b/apps/api-server/internal/services/es2_client.go index 5d9aa58..007917e 100644 --- a/apps/api-server/internal/services/es2_client.go +++ b/apps/api-server/internal/services/es2_client.go @@ -1,10 +1,8 @@ package services import ( - "bytes" "context" "crypto/tls" - "encoding/json" "fmt" "net/http" "time" @@ -34,65 +32,6 @@ func NewES2Service(cfg *config.ES2Config) *ES2Service { } } -// ES2 request/response types -type DownloadOrderRequest struct { - EID string `json:"eid"` - ICCID string `json:"iccid"` - ProfileType string `json:"profileType"` - Confirmation bool `json:"confirmation"` - Metadata map[string]string `json:"metadata"` -} - -type DownloadOrderResponse struct { - ICCID string `json:"iccid"` - ProfileID string `json:"profileId"` - ActivationCode string `json:"activationCode"` - ConfirmationAddress string `json:"confirmationAddress"` -} - -type ActivationRequest struct { - EID string `json:"eid"` - ProfileID string `json:"profileId"` -} - -type DeactivationRequest struct { - EID string `json:"eid"` - ProfileID string `json:"profileId"` -} - -type DeletionRequest struct { - EID string `json:"eid"` - ProfileID string `json:"profileId"` -} - -type ProfileStatusResponse struct { - ICCID string `json:"iccid"` - ProfileID string `json:"profileId"` - ProfileName string `json:"profileName"` - State string `json:"state"` - Operator string `json:"operator"` -} - -type ListProfilesResponse struct { - Profiles []ProfileStatusResponse `json:"profiles"` -} - -// ProfileInfo represents eSIM profile information -type ProfileInfo struct { - ICCID string `json:"iccid"` - ProfileID string `json:"profileId"` - ProfileName string `json:"profileName"` - State string `json:"state"` - Operator string `json:"operator"` - Activation ProfileActivation `json:"activation"` -} - -// ProfileActivation represents profile activation details -type ProfileActivation struct { - ActivationCode string `json:"activationCode"` - ConfAddress string `json:"confirmationAddress"` -} - // ProvisionProfile provisions an eSIM profile for a subscriber func (e *ES2Service) ProvisionProfile(ctx context.Context, subscriber *models.Subscriber) (*ProfileInfo, error) { // Check if subscriber has EUICCID @@ -134,254 +73,3 @@ func (e *ES2Service) ProvisionProfile(ctx context.Context, subscriber *models.Su return profileInfo, nil } - -// downloadProfile is an internal method to download a profile -func (e *ES2Service) downloadProfile(ctx context.Context, req DownloadOrderRequest) (*DownloadOrderResponse, error) { - jsonData, err := json.Marshal(req) - if err != nil { - return nil, fmt.Errorf("failed to marshal download request: %w", err) - } - - url := fmt.Sprintf("%s/es2/download", e.config.BaseURL) - httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData)) - if err != nil { - return nil, fmt.Errorf("failed to create HTTP request: %w", err) - } - - httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("X-Request-ID", generateRequestID()) - httpReq.Header.Set("Authorization", "Bearer "+e.config.APIKey) - - resp, err := e.httpClient.Do(httpReq) - if err != nil { - return nil, fmt.Errorf("failed to send download request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("ES2 server returned status %d", resp.StatusCode) - } - - var response DownloadOrderResponse - if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { - return nil, fmt.Errorf("failed to decode download response: %w", err) - } - - return &response, nil -} - -// ActivateProfile activates an eSIM profile -func (e *ES2Service) ActivateProfile(ctx context.Context, euiccID, profileID string) error { - req := ActivationRequest{ - EID: euiccID, - ProfileID: profileID, - } - - return e.sendActivationRequest(ctx, req, "activate") -} - -// DeactivateProfile deactivates an eSIM profile -func (e *ES2Service) DeactivateProfile(ctx context.Context, euiccID, profileID string) error { - req := DeactivationRequest{ - EID: euiccID, - ProfileID: profileID, - } - - return e.sendDeactivationRequest(ctx, req) -} - -// DeleteProfile deletes an eSIM profile -func (e *ES2Service) DeleteProfile(ctx context.Context, euiccID, profileID string) error { - req := DeletionRequest{ - EID: euiccID, - ProfileID: profileID, - } - - return e.sendDeletionRequest(ctx, req) -} - -// GetProfileStatus retrieves the status of an eSIM profile -func (e *ES2Service) GetProfileStatus(ctx context.Context, euiccID, profileID string) (*ProfileInfo, error) { - url := fmt.Sprintf("%s/es2/eid/%s/profile/%s/status", e.config.BaseURL, euiccID, profileID) - httpReq, err := http.NewRequestWithContext(ctx, "GET", url, nil) - if err != nil { - return nil, fmt.Errorf("failed to create HTTP request: %w", err) - } - - httpReq.Header.Set("X-Request-ID", generateRequestID()) - httpReq.Header.Set("Authorization", "Bearer "+e.config.APIKey) - - resp, err := e.httpClient.Do(httpReq) - if err != nil { - return nil, fmt.Errorf("failed to send status request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("ES2 server returned status %d", resp.StatusCode) - } - - var response ProfileStatusResponse - if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { - return nil, fmt.Errorf("failed to decode status response: %w", err) - } - - profileInfo := &ProfileInfo{ - ICCID: response.ICCID, - ProfileID: response.ProfileID, - ProfileName: response.ProfileName, - State: response.State, - Operator: response.Operator, - } - - return profileInfo, nil -} - -// ListProfiles lists all profiles on an eUICC -func (e *ES2Service) ListProfiles(ctx context.Context, euiccID string) ([]*ProfileInfo, error) { - url := fmt.Sprintf("%s/es2/eid/%s/profiles", e.config.BaseURL, euiccID) - httpReq, err := http.NewRequestWithContext(ctx, "GET", url, nil) - if err != nil { - return nil, fmt.Errorf("failed to create HTTP request: %w", err) - } - - httpReq.Header.Set("X-Request-ID", generateRequestID()) - httpReq.Header.Set("Authorization", "Bearer "+e.config.APIKey) - - resp, err := e.httpClient.Do(httpReq) - if err != nil { - return nil, fmt.Errorf("failed to send list request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("ES2 server returned status %d", resp.StatusCode) - } - - var response ListProfilesResponse - if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { - return nil, fmt.Errorf("failed to decode list response: %w", err) - } - - var profiles []*ProfileInfo - for _, profile := range response.Profiles { - profileInfo := &ProfileInfo{ - ICCID: profile.ICCID, - ProfileID: profile.ProfileID, - ProfileName: profile.ProfileName, - State: profile.State, - Operator: profile.Operator, - } - profiles = append(profiles, profileInfo) - } - - return profiles, nil -} - -// Helper methods for ES2 operations -func (e *ES2Service) sendActivationRequest(ctx context.Context, req ActivationRequest, action string) error { - jsonData, err := json.Marshal(req) - if err != nil { - return fmt.Errorf("failed to marshal activation request: %w", err) - } - - url := fmt.Sprintf("%s/es2/activate", e.config.BaseURL) - httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData)) - if err != nil { - return fmt.Errorf("failed to create HTTP request: %w", err) - } - - httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("X-Request-ID", generateRequestID()) - httpReq.Header.Set("Authorization", "Bearer "+e.config.APIKey) - - resp, err := e.httpClient.Do(httpReq) - if err != nil { - return fmt.Errorf("failed to send activation request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("ES2 server returned status %d", resp.StatusCode) - } - - return nil -} - -func (e *ES2Service) sendDeactivationRequest(ctx context.Context, req any) error { - jsonData, err := json.Marshal(req) - if err != nil { - return fmt.Errorf("failed to marshal deactivation request: %w", err) - } - - url := fmt.Sprintf("%s/es2/deactivate", e.config.BaseURL) - httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData)) - if err != nil { - return fmt.Errorf("failed to create HTTP request: %w", err) - } - - httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("X-Request-ID", generateRequestID()) - httpReq.Header.Set("Authorization", "Bearer "+e.config.APIKey) - - resp, err := e.httpClient.Do(httpReq) - if err != nil { - return fmt.Errorf("failed to send deactivation request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("ES2 server returned status %d", resp.StatusCode) - } - - return nil -} - -func (e *ES2Service) sendDeletionRequest(ctx context.Context, req DeletionRequest) error { - jsonData, err := json.Marshal(req) - if err != nil { - return fmt.Errorf("failed to marshal deletion request: %w", err) - } - - url := fmt.Sprintf("%s/es2/delete", e.config.BaseURL) - httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData)) - if err != nil { - return fmt.Errorf("failed to create HTTP request: %w", err) - } - - httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("X-Request-ID", generateRequestID()) - httpReq.Header.Set("Authorization", "Bearer "+e.config.APIKey) - - resp, err := e.httpClient.Do(httpReq) - if err != nil { - return fmt.Errorf("failed to send deletion request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("ES2 server returned status %d", resp.StatusCode) - } - - return nil -} - -// ValidateEID validates an EID format -func (e *ES2Service) ValidateEID(eid string) error { - if len(eid) != 32 { - return fmt.Errorf("invalid EID length: expected 32 characters, got %d", len(eid)) - } - - // Additional validation logic can be added here - return nil -} - -// ValidateICCID validates an ICCID format -func (e *ES2Service) ValidateICCID(iccid string) error { - if len(iccid) < 18 || len(iccid) > 22 { - return fmt.Errorf("invalid ICCID length: expected 18-22 characters, got %d", len(iccid)) - } - - // Additional validation logic can be added here - return nil -} diff --git a/apps/api-server/internal/services/es2_helpers.go b/apps/api-server/internal/services/es2_helpers.go new file mode 100644 index 0000000..6b4ee7e --- /dev/null +++ b/apps/api-server/internal/services/es2_helpers.go @@ -0,0 +1,123 @@ +package services + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "time" +) + +// Helper methods for ES2 operations +func (e *ES2Service) sendActivationRequest(ctx context.Context, req ActivationRequest, action string) error { + jsonData, err := json.Marshal(req) + if err != nil { + return fmt.Errorf("failed to marshal activation request: %w", err) + } + + url := fmt.Sprintf("%s/es2/activate", e.config.BaseURL) + httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData)) + if err != nil { + return fmt.Errorf("failed to create HTTP request: %w", err) + } + + httpReq.Header.Set("Content-Type", "application/json") + httpReq.Header.Set("X-Request-ID", generateRequestID()) + httpReq.Header.Set("Authorization", "Bearer "+e.config.APIKey) + + resp, err := e.httpClient.Do(httpReq) + if err != nil { + return fmt.Errorf("failed to send activation request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("ES2 server returned status %d", resp.StatusCode) + } + + return nil +} + +func (e *ES2Service) sendDeactivationRequest(ctx context.Context, req interface{}) error { + jsonData, err := json.Marshal(req) + if err != nil { + return fmt.Errorf("failed to marshal deactivation request: %w", err) + } + + url := fmt.Sprintf("%s/es2/deactivate", e.config.BaseURL) + httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData)) + if err != nil { + return fmt.Errorf("failed to create HTTP request: %w", err) + } + + httpReq.Header.Set("Content-Type", "application/json") + httpReq.Header.Set("X-Request-ID", generateRequestID()) + httpReq.Header.Set("Authorization", "Bearer "+e.config.APIKey) + + resp, err := e.httpClient.Do(httpReq) + if err != nil { + return fmt.Errorf("failed to send deactivation request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("ES2 server returned status %d", resp.StatusCode) + } + + return nil +} + +func (e *ES2Service) sendDeletionRequest(ctx context.Context, req DeletionRequest) error { + jsonData, err := json.Marshal(req) + if err != nil { + return fmt.Errorf("failed to marshal deletion request: %w", err) + } + + url := fmt.Sprintf("%s/es2/delete", e.config.BaseURL) + httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData)) + if err != nil { + return fmt.Errorf("failed to create HTTP request: %w", err) + } + + httpReq.Header.Set("Content-Type", "application/json") + httpReq.Header.Set("X-Request-ID", generateRequestID()) + httpReq.Header.Set("Authorization", "Bearer "+e.config.APIKey) + + resp, err := e.httpClient.Do(httpReq) + if err != nil { + return fmt.Errorf("failed to send deletion request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("ES2 server returned status %d", resp.StatusCode) + } + + return nil +} + +// ValidateEID validates an EID format +func (e *ES2Service) ValidateEID(eid string) error { + if len(eid) != 32 { + return fmt.Errorf("invalid EID length: expected 32 characters, got %d", len(eid)) + } + + // Additional validation logic can be added here + return nil +} + +// ValidateICCID validates an ICCID format +func (e *ES2Service) ValidateICCID(iccid string) error { + if len(iccid) < 18 || len(iccid) > 22 { + return fmt.Errorf("invalid ICCID length: expected 18-22 characters, got %d", len(iccid)) + } + + // Additional validation logic can be added here + return nil +} + +// generateRequestID generates a unique request ID +func generateRequestID() string { + return fmt.Sprintf("%d", time.Now().UnixNano()) +} diff --git a/apps/api-server/internal/services/es2_operations.go b/apps/api-server/internal/services/es2_operations.go new file mode 100644 index 0000000..63e52c2 --- /dev/null +++ b/apps/api-server/internal/services/es2_operations.go @@ -0,0 +1,74 @@ +package services + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" +) + +// downloadProfile is an internal method to download a profile +func (e *ES2Service) downloadProfile(ctx context.Context, req DownloadOrderRequest) (*DownloadOrderResponse, error) { + jsonData, err := json.Marshal(req) + if err != nil { + return nil, fmt.Errorf("failed to marshal download request: %w", err) + } + + url := fmt.Sprintf("%s/es2/download", e.config.BaseURL) + httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData)) + if err != nil { + return nil, fmt.Errorf("failed to create HTTP request: %w", err) + } + + httpReq.Header.Set("Content-Type", "application/json") + httpReq.Header.Set("X-Request-ID", generateRequestID()) + httpReq.Header.Set("Authorization", "Bearer "+e.config.APIKey) + + resp, err := e.httpClient.Do(httpReq) + if err != nil { + return nil, fmt.Errorf("failed to send download request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("ES2 server returned status %d", resp.StatusCode) + } + + var response DownloadOrderResponse + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return nil, fmt.Errorf("failed to decode download response: %w", err) + } + + return &response, nil +} + +// ActivateProfile activates an eSIM profile +func (e *ES2Service) ActivateProfile(ctx context.Context, euiccID, profileID string) error { + req := ActivationRequest{ + EID: euiccID, + ProfileID: profileID, + } + + return e.sendActivationRequest(ctx, req, "activate") +} + +// DeactivateProfile deactivates an eSIM profile +func (e *ES2Service) DeactivateProfile(ctx context.Context, euiccID, profileID string) error { + req := DeactivationRequest{ + EID: euiccID, + ProfileID: profileID, + } + + return e.sendDeactivationRequest(ctx, req) +} + +// DeleteProfile deletes an eSIM profile +func (e *ES2Service) DeleteProfile(ctx context.Context, euiccID, profileID string) error { + req := DeletionRequest{ + EID: euiccID, + ProfileID: profileID, + } + + return e.sendDeletionRequest(ctx, req) +} diff --git a/apps/api-server/internal/services/es2_queries.go b/apps/api-server/internal/services/es2_queries.go new file mode 100644 index 0000000..08ba2ee --- /dev/null +++ b/apps/api-server/internal/services/es2_queries.go @@ -0,0 +1,86 @@ +package services + +import ( + "context" + "encoding/json" + "fmt" + "net/http" +) + +// GetProfileStatus retrieves the status of an eSIM profile +func (e *ES2Service) GetProfileStatus(ctx context.Context, euiccID, profileID string) (*ProfileInfo, error) { + url := fmt.Sprintf("%s/es2/eid/%s/profile/%s/status", e.config.BaseURL, euiccID, profileID) + httpReq, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, fmt.Errorf("failed to create HTTP request: %w", err) + } + + httpReq.Header.Set("X-Request-ID", generateRequestID()) + httpReq.Header.Set("Authorization", "Bearer "+e.config.APIKey) + + resp, err := e.httpClient.Do(httpReq) + if err != nil { + return nil, fmt.Errorf("failed to send status request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("ES2 server returned status %d", resp.StatusCode) + } + + var response ProfileStatusResponse + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return nil, fmt.Errorf("failed to decode status response: %w", err) + } + + profileInfo := &ProfileInfo{ + ICCID: response.ICCID, + ProfileID: response.ProfileID, + ProfileName: response.ProfileName, + State: response.State, + Operator: response.Operator, + } + + return profileInfo, nil +} + +// ListProfiles lists all profiles on an eUICC +func (e *ES2Service) ListProfiles(ctx context.Context, euiccID string) ([]*ProfileInfo, error) { + url := fmt.Sprintf("%s/es2/eid/%s/profiles", e.config.BaseURL, euiccID) + httpReq, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, fmt.Errorf("failed to create HTTP request: %w", err) + } + + httpReq.Header.Set("X-Request-ID", generateRequestID()) + httpReq.Header.Set("Authorization", "Bearer "+e.config.APIKey) + + resp, err := e.httpClient.Do(httpReq) + if err != nil { + return nil, fmt.Errorf("failed to send list request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("ES2 server returned status %d", resp.StatusCode) + } + + var response ListProfilesResponse + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return nil, fmt.Errorf("failed to decode list response: %w", err) + } + + var profiles []*ProfileInfo + for _, profile := range response.Profiles { + profileInfo := &ProfileInfo{ + ICCID: profile.ICCID, + ProfileID: profile.ProfileID, + ProfileName: profile.ProfileName, + State: profile.State, + Operator: profile.Operator, + } + profiles = append(profiles, profileInfo) + } + + return profiles, nil +} diff --git a/apps/api-server/internal/services/es2_types.go b/apps/api-server/internal/services/es2_types.go new file mode 100644 index 0000000..5c9e23a --- /dev/null +++ b/apps/api-server/internal/services/es2_types.go @@ -0,0 +1,60 @@ +package services + +// ES2 request/response types +type DownloadOrderRequest struct { + EID string `json:"eid"` + ICCID string `json:"iccid"` + ProfileType string `json:"profileType"` + Confirmation bool `json:"confirmation"` + Metadata map[string]string `json:"metadata"` +} + +type DownloadOrderResponse struct { + ICCID string `json:"iccid"` + ProfileID string `json:"profileId"` + ActivationCode string `json:"activationCode"` + ConfirmationAddress string `json:"confirmationAddress"` +} + +type ActivationRequest struct { + EID string `json:"eid"` + ProfileID string `json:"profileId"` +} + +type DeactivationRequest struct { + EID string `json:"eid"` + ProfileID string `json:"profileId"` +} + +type DeletionRequest struct { + EID string `json:"eid"` + ProfileID string `json:"profileId"` +} + +type ProfileStatusResponse struct { + ICCID string `json:"iccid"` + ProfileID string `json:"profileId"` + ProfileName string `json:"profileName"` + State string `json:"state"` + Operator string `json:"operator"` +} + +type ListProfilesResponse struct { + Profiles []ProfileStatusResponse `json:"profiles"` +} + +// ProfileInfo represents eSIM profile information +type ProfileInfo struct { + ICCID string `json:"iccid"` + ProfileID string `json:"profileId"` + ProfileName string `json:"profileName"` + State string `json:"state"` + Operator string `json:"operator"` + Activation ProfileActivation `json:"activation"` +} + +// ProfileActivation represents profile activation details +type ProfileActivation struct { + ActivationCode string `json:"activationCode"` + ConfAddress string `json:"confirmationAddress"` +}