Files
plane/apps/monitor/lib/api/api.go
T
e939dc3ef7 [PRIME-21]: Enterprise Plan for self hosted and airgapped (#4141)
* feat: add enterprise license activation and deactivation endpoints

- Implemented `EnterpriseLicenseActivateEndpoint` and `EnterpriseLicenseDeactivateEndpoint` for managing enterprise licenses.
- Updated URL routing to include new endpoints for activating and deactivating enterprise licenses.
- Added necessary request handling and response structures for enterprise license operations.
- Enhanced existing license management logic to accommodate enterprise-specific requirements.

* feat: add enterprise plan invite and update handling

- Implemented `handle_enterprise_plan_invite_case` to manage invites under the enterprise plan.
- Added `handle_enterprise_plan_update_case` for role updates within the enterprise plan.
- Updated `handle_cloud_payments` to incorporate new enterprise plan handling logic for invites and role updates.

* feat: add enterprise license management endpoints

- Introduced new endpoints for managing enterprise licenses, including `InstanceLicenseSyncEndpoint`, `EnterpriseModifySeatsEndpoint`, `EnterpriseSubscriptionPortalEndpoint`, and `EnterpriseSubscriptionProrationPreviewEndpoint`.
- Updated URL routing to accommodate the new endpoints for enterprise license operations.
- Enhanced the views and API interactions to support enterprise license modifications and subscription management.

* feat: enhance enterprise license activation and management

- Updated `EnterpriseLicenseActivateEndpoint` to allow any user to access the activation endpoint.
- Introduced new airgapped activation handler for enterprise licenses, enabling file uploads and processing of activation files.
- Added routes for enterprise license activation in airgapped mode, including file handling and database population.
- Enhanced error handling and response structures for better user feedback during activation processes.

* feat: implement instance license management features

- Added new components and hooks for managing instance licenses, including activation via license key and file uploads.
- Introduced `InstanceManagementStore` to handle license operations and state management.
- Created UI components for license management, including forms for entering license keys and uploading license files.
- Enhanced API interactions for license activation and deactivation, with improved error handling and user feedback.
- Updated routing and layout for the instance management page to accommodate new features.

* refactor: replace regular imports with type imports for improved clarity across multiple components

* fix: admin build

* refactor: update enterprise license API paths and enhance error handling

- Changed API paths for enterprise license endpoints to include "instances" in the URL structure.
- Improved error handling in the EnterpriseLicenseActivateEndpoint and EnterpriseLicenseDeactivateEndpoint to return more specific error messages based on response status codes.
- Added logging configuration for "plane.payments" in both local and production settings.

* feat: add InstanceLicenseEndpoint for retrieving current enterprise license plan

- Introduced InstanceLicenseEndpoint to fetch the current enterprise license plan from the payment server.
- Updated URL routing to include the new endpoint for instance licenses.
- Enhanced error handling for the new endpoint to manage various response scenarios.

* feat: add billing and user management pages, update sidebar menu

- Introduced new routes for billing and user management in the admin dashboard.
- Updated the sidebar menu to include links for the new billing and user management pages.
- Refactored existing components to utilize a new PageWrapper for consistent layout across pages.
- Enhanced the AI, authentication, and image pages with the new PageWrapper for improved UI consistency.

* fix: adjust icon sizes

* feat: enterprise license activation UI

* fix: remove unnecessary shrink-0 class from header actions div in PageWrapper

* fix: update error handling in enterprise license endpoints

- Removed unnecessary newline in the Python API view for instance licenses.
- Changed the response in the Go handler to return a success status with a message when no enterprise license exists, instead of an internal server error.

* feat: enhance enterprise license management and update dependencies

* refactor: simplify EnterprisePlanCard and enhance button handling

- Removed unnecessary props and state management from EnterprisePlanCard.
- Updated PlanCard to conditionally render the button.
- Enhanced BillingActionsButton to provide tooltips for Enterprise plan users.
- Adjusted limit-reached-info to reflect Enterprise plan status in seat management.

* style: update modal styling for enterprise license success component

* refactor: streamline enterprise license deactivation process

- Removed the requirement for a license key in the Python API view.
- Simplified the Go handler by eliminating unnecessary payload parsing and improved the response structure for cases with no existing enterprise license.

* refactor: remove InstanceLicenseActions component and clean up related code

- Deleted the InstanceLicenseActions component to streamline the enterprise license management.
- Updated EnterprisePlanCard to conditionally render components based on the subscription status.
- Removed unused props and functions related to trial period checks from the instance management store and types.

* refactor: simplify GetEnterpriseLicenseCurrentPlan response structure

- Removed unnecessary fields from the response for both existing and new enterprise licenses.
- Streamlined the JSON output to focus on essential information, enhancing clarity and reducing payload size.

* feat: implement seat management functionality for enterprise licenses

- Added InstanceLicenseActions component to handle license synchronization and deactivation.
- Introduced SeatsManagementButton for managing seat additions and removals.
- Created modals and forms for adding and removing seats, including proration previews.
- Updated EnterprisePlanCard to integrate new seat management features.
- Enhanced instance management store with methods for updating subscription details and managing seats.

* feat: enhance enterprise plan user guidance and UI updates

* refactor: update instance management service usage and rename seat form components

* fix: closure issues in enterprise plan card and instance management store

* feat: add endpoint to remove unused seats from enterprise licenses

- Introduced EnterpriseLicenseRemoveUnusedSeatsEndpoint to handle the removal of unused seats.
- Updated URL routing to include the new endpoint.
- Refactored user count retrieval logic to streamline seat management processes.

* fix: remove duplicate workspaceSlug prop from RemoveUnusedSeatsModal in GlobalModals component

* chore: change the enterprise member count

* feat: enhance seat management UI and logic for subscription levels

- Updated the SelectSeatsStep component to display different messages based on subscription level (workspace vs instance).
- Modified the ConfirmPriceStep to reflect the subscription level in the confirmation message.
- Added subscription level handling in the AddSeatsForm component.
- Improved the invitation description to differentiate between Enterprise and non-Enterprise plans.
- Introduced a new field for allowed total users in the TMemberInviteCheck type for Enterprise plans.
- Refactored styles for better UI consistency in the NumberInputWithControls component.

* feat: implement enterprise plan invite check for workspace

- Added a method to check the allowed total users for inviting new members based on the enterprise plan.
- Integrated the invite check into the existing endpoint response, providing clarity on user limits for enterprise workspaces.
- Enhanced the logic to count current active and invited users, ensuring accurate seat management for enterprise licenses.

* refactor: remove unused import in workspace invite check endpoint

- Eliminated the unused import of handle_enterprise_plan_invite_case from the invite.py file, streamlining the code and improving maintainability.

* fix: update enterprise plan user limit check in invitation description

* fix: format

---------

Co-authored-by: Prateek Shourya <[email protected]>
Co-authored-by: b-saikrishnakanth <[email protected]>
2025-12-24 13:19:03 +05:30

662 lines
20 KiB
Go

package prime_api
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
)
type IPrimeMonitorApi interface {
// Getters for the api
IsAirgapped() bool
AppVersion() string
ApiHostname() string
PostServiceStatus(StatusPayload) ErrorCode
GetFeatureFlags(licenseKey string) (*FlagDataResponse, *APIError)
ActivateInstance() *APIError
DeactivateInstance() *APIError
RetrievePlans(string) (*[]Product, *APIError)
RetrievePaymentLink(RetrievePaymentLinkPayload) (*RetrievePaymentLinkResponse, *APIError)
UpdateSubcription(SeatUpdatePayload) (*SeatUpdateResponse, *APIError)
SyncWorkspace(WorkspaceSyncPayload) (*WorkspaceActivationResponse, *APIError)
DeactivateLicense(LicenseDeactivatePayload) (*WorkspaceActivationResponse, *APIError)
ActivateWorkspace(WorkspaceActivationPayload) (*WorkspaceActivationResponse, *APIError)
ActivateFreeWorkspace(WorkspaceActivationPayload) (*WorkspaceActivationResponse, *APIError)
InitializeInstance(CredentialsPayload) (SetupResponse, *APIError)
GetSubscriptionDetails(WorkspaceSubscriptionPayload) (*WorkspaceSubscriptionResponse, *APIError)
GetProrationPreview(ProrationPreviewPayload) (*ProrationPreviewResponse, *APIError)
// Enterprise License
EnterpriseLicenseActivate(EnterpriseLicenseActivatePayload) (*EnterpriseLicenseActivateResponse, *APIError)
SyncEnterpriseLicense(EnterpriseLicenseSyncPayload) (*EnterpriseLicenseActivateResponse, *APIError)
GetEnterpriseFeatureFlags(licenseKey string) (*FlagDataResponse, *APIError)
DeactivateEnterpriseLicense(LicenseDeactivatePayload) *APIError
UpdateEnterpriseLicenseSeats(UpdateEnterpriseLicenseSeatsPayload) (*SeatUpdateResponse, *APIError)
GetEnterpriseLicensePortal() (*EnterpriseLicensePortalResponse, *APIError)
GetEnterpriseLicenseProrationPreview(UpdateEnterpriseLicenseSeatsPayload) (*ProrationPreviewResponse, *APIError)
}
type LicenseDeactivatePayload struct {
WorkspaceSlug string `json:"workspace_slug"`
WorkspaceID string `json:"workspace_id"`
LicenseKey string `json:"license_key"`
}
type PrimeMonitorApi struct {
host string
instanceId string
appVersion string
client string
version string
machineSignature string
}
type CredentialsPayload struct {
ServerId string
Domain string
AppVersion string
}
type WorkspaceSubscriptionPayload struct {
WorkspaceId string `json:"workspace_id"`
LicenseKey string `json:"license_key"`
}
type WorkspaceSubscriptionResponse struct {
Product string `json:"product"`
CurrentPeriodEnd string `json:"current_period_end_date"`
SubscriptionExists bool `json:"subscription_exists"`
Url string `json:"url"`
}
type ProrationPreviewPayload struct {
WorkspaceId string `json:"workspace_id"`
Quantity int `json:"quantity"`
WorkspaceSlug string `json:"workspace_slug"`
LicenseKey string `json:"license_key"`
}
type ProrationPreviewResponse struct {
QuantityDifference int `json:"quantity_difference"`
PerSeatProrationAmount float64 `json:"per_seat_prorated_amount"`
NewQuantity int `json:"new_quantity"`
TotalProratedAmount float64 `json:"total_prorated_amount"`
CurrentQuantity int `json:"current_quantity"`
CurrentPriceAmount float64 `json:"current_price_amount"`
CurrentPriceInterval string `json:"current_price_interval"`
}
type SetupResponse struct {
Domain string `json:"domain"`
InstanceId string `json:"instance_id"`
Host string `json:"prime_host"`
Version string `json:"version"`
}
type FlagsPayload struct {
EncryptedData string `json:"encrypted_data"`
}
// Add this struct to handle the error response from the server
type ErrorResponse struct {
Message string `json:"message"`
Error string `json:"error"`
}
func NewMonitorApi(host, machineSignature, instanceId, appVersion string) IPrimeMonitorApi {
return &PrimeMonitorApi{
host: host,
instanceId: instanceId,
appVersion: appVersion,
client: "Prime-Monitor",
version: appVersion,
machineSignature: machineSignature,
}
}
// We don't need this for the normal api, but we need it for the airgapped api
func (api *PrimeMonitorApi) ApiHostname() string {
return ""
}
func (api *PrimeMonitorApi) AppVersion() string {
return api.appVersion
}
func (api *PrimeMonitorApi) IsAirgapped() bool {
return false
}
func (api *PrimeMonitorApi) SetClient(client string) {
api.client = client
}
var (
API_PREFIX = "/api/v2"
MONITOR_ENDPOINT = API_PREFIX + "/monitor/"
FEATURE_FLAGS = API_PREFIX + "/flags/"
INSTANCE_PREFIX = API_PREFIX + "/instances"
ACTIVATE_INSTANCE = INSTANCE_PREFIX + "/activate/"
DEACTIVATE_INSTANCE = INSTANCE_PREFIX + "/deactivate/"
UPGRADE_INSTANCE = INSTANCE_PREFIX + "/upgrade/"
FREE_WORKSPACE_ACTIVATE = API_PREFIX + "/licenses/initialize/"
WORKSPACE_ACTIVATE = API_PREFIX + "/licenses/activate/"
SYNC_WORKSPACE = API_PREFIX + "/licenses/sync/"
UPDATE_SUBSCRIPTION = API_PREFIX + "/modify-subscriptions/"
SETUP_ENDPOINT = API_PREFIX + "/instances/initialize/"
SUBSCRIPTION_PORTAL = API_PREFIX + "/subscription-portal/"
RETRIEVE_PLANS = API_PREFIX + "/instances/products/"
RETRIEVE_PAYMENT_LINK = API_PREFIX + "/instances/payment-link/"
DEACTIVATE_LICENSE_ENDPOINT = API_PREFIX + "/licenses/deactivate/"
PRORATION_PREVIEW = API_PREFIX + "/subscriptions/proration-preview/"
// Enterprise License
ENTERPRISE_LICENSE_ACTIVATE = API_PREFIX + "/enterprise/licenses/activate/"
SYNC_ENTERPRISE_LICENSE = API_PREFIX + "/enterprise/licenses/sync/"
DEACTIVATE_ENTERPRISE_LICENSE = API_PREFIX + "/enterprise/licenses/deactivate/"
UPDATE_ENTERPRISE_LICENSE_SEATS = API_PREFIX + "/enterprise/licenses/update-subscriptions/"
ENTERPRISE_SUBSCRIPTION_PORTAL = API_PREFIX + "/enterprise/licenses/subscription-portal/"
ENTERPRISE_PRORATION_PREVIEW = API_PREFIX + "/enterprise/licenses/subscription-proration-preview/"
)
/* ----------------------- Controller Methods ------------------------------ */
// Posts the status of the services given, to the prime server, returns error if
// hinderer, else doesn't return anything
func (api *PrimeMonitorApi) PostServiceStatus(payload StatusPayload) ErrorCode {
_, err := api.post(api.host+MONITOR_ENDPOINT, payload)
if err != nil {
return UNABLE_TO_POST_SERVICE_STATUS
}
return 0
}
func (api *PrimeMonitorApi) DeactivateLicense(payload LicenseDeactivatePayload) (*WorkspaceActivationResponse, *APIError) {
resp, apiError := api.post(api.host+DEACTIVATE_LICENSE_ENDPOINT, payload)
if apiError != nil {
return nil, apiError
}
data := WorkspaceActivationResponse{}
if err := json.Unmarshal(resp, &data); err != nil {
return nil, &APIError{
Error: fmt.Sprintf("Error unmarshaling response: %v", err),
Success: false,
}
}
return &data, nil
}
func (api *PrimeMonitorApi) ActivateFreeWorkspace(payload WorkspaceActivationPayload) (*WorkspaceActivationResponse, *APIError) {
resp, apiError := api.post(api.host+FREE_WORKSPACE_ACTIVATE, payload)
if apiError != nil {
return nil, apiError
}
data := WorkspaceActivationResponse{}
if err := json.Unmarshal(resp, &data); err != nil {
return nil, &APIError{
Error: fmt.Sprintf("Error unmarshaling response: %v", err),
Success: false,
}
}
return &data, nil
}
// Activating a paid licensed workspace
func (api *PrimeMonitorApi) ActivateWorkspace(payload WorkspaceActivationPayload) (*WorkspaceActivationResponse, *APIError) {
resp, apiError := api.post(api.host+WORKSPACE_ACTIVATE, payload)
if apiError != nil {
return nil, apiError
}
data := WorkspaceActivationResponse{}
if err := json.Unmarshal(resp, &data); err != nil {
return nil, &APIError{
Error: fmt.Sprintf("Error unmarshaling response: %v", err),
Success: false,
}
}
return &data, nil
}
func (api *PrimeMonitorApi) GetFeatureFlags(licenseKey string) (*FlagDataResponse, *APIError) {
flagData, apiError := api.post(api.host+FEATURE_FLAGS, map[string]string{
"license_key": licenseKey,
"version": api.version,
})
if apiError != nil {
return nil, apiError
}
data := FlagDataResponse{}
if err := json.Unmarshal(flagData, &data); err != nil {
return nil, &APIError{
Error: fmt.Sprintf("Error unmarshaling response: %v", err),
Success: false,
}
}
return &data, nil
}
func (api *PrimeMonitorApi) RetrievePlans(quantity string) (*[]Product, *APIError) {
resp, apiError := api.get(api.host+RETRIEVE_PLANS, map[string]string{
"quantity": quantity,
})
if apiError != nil {
return nil, apiError
}
data := []Product{}
if err := json.Unmarshal(resp, &data); err != nil {
return nil, &APIError{
Error: fmt.Sprintf("Error unmarshaling response: %v", err),
Success: false,
}
}
return &data, nil
}
func (api *PrimeMonitorApi) RetrievePaymentLink(payload RetrievePaymentLinkPayload) (*RetrievePaymentLinkResponse, *APIError) {
resp, apiError := api.post(api.host+RETRIEVE_PAYMENT_LINK, payload)
if apiError != nil {
return nil, apiError
}
data := RetrievePaymentLinkResponse{}
if err := json.Unmarshal(resp, &data); err != nil {
return nil, &APIError{
Error: fmt.Sprintf("Error unmarshaling response: %v", err),
Success: false,
}
}
return &data, nil
}
func (api *PrimeMonitorApi) SyncWorkspace(payload WorkspaceSyncPayload) (*WorkspaceActivationResponse, *APIError) {
resp, apiError := api.post(api.host+SYNC_WORKSPACE, payload)
if apiError != nil {
return nil, apiError
}
data := WorkspaceActivationResponse{}
if err := json.Unmarshal(resp, &data); err != nil {
return nil, &APIError{
Error: fmt.Sprintf("Error unmarshaling response: %v", err),
Success: false,
}
}
return &data, nil
}
func (api *PrimeMonitorApi) ActivateInstance() *APIError {
_, apiError := api.post(api.host+ACTIVATE_INSTANCE, nil)
if apiError != nil {
return apiError
}
return nil
}
func (api *PrimeMonitorApi) DeactivateInstance() *APIError {
_, apiError := api.post(api.host+DEACTIVATE_INSTANCE, nil)
if apiError != nil {
return apiError
}
return nil
}
func (api *PrimeMonitorApi) UpgradeInstance() *APIError {
_, apiError := api.post(api.host+UPGRADE_INSTANCE, nil)
if apiError != nil {
return apiError
}
return nil
}
func (api *PrimeMonitorApi) UpdateSubcription(payload SeatUpdatePayload) (*SeatUpdateResponse, *APIError) {
resp, apiError := api.post(api.host+UPDATE_SUBSCRIPTION, payload)
if apiError != nil {
return nil, apiError
}
data := SeatUpdateResponse{}
if err := json.Unmarshal(resp, &data); err != nil {
return nil, &APIError{
Error: fmt.Sprintf("Error unmarshaling response: %v", err),
Success: false,
}
}
return &data, nil
}
func (api *PrimeMonitorApi) InitializeInstance(payload CredentialsPayload) (SetupResponse, *APIError) {
resp, apiError := api.post(api.host+SETUP_ENDPOINT, map[string]string{
"machine_signature": payload.ServerId,
"domain": payload.Domain,
"app_version": payload.AppVersion,
"deploy_platform": "KUBERNETES",
})
if apiError != nil {
return SetupResponse{}, apiError
}
setupResponse := SetupResponse{}
if err := json.Unmarshal(resp, &setupResponse); err != nil {
return SetupResponse{}, &APIError{
Error: fmt.Sprintf("Error unmarshaling response: %v", err),
Success: false,
}
}
return setupResponse, nil
}
func (api *PrimeMonitorApi) GetSubscriptionDetails(payload WorkspaceSubscriptionPayload) (*WorkspaceSubscriptionResponse, *APIError) {
resp, apiError := api.post(api.host+SUBSCRIPTION_PORTAL, payload)
if apiError != nil {
return nil, apiError
}
data := WorkspaceSubscriptionResponse{}
if err := json.Unmarshal(resp, &data); err != nil {
return nil, &APIError{
Error: fmt.Sprintf("Error unmarshaling response: %v", err),
Success: false,
}
}
return &data, nil
}
func (api *PrimeMonitorApi) GetProrationPreview(payload ProrationPreviewPayload) (*ProrationPreviewResponse, *APIError) {
// Make the request
resp, apiError := api.post(api.host+PRORATION_PREVIEW, payload)
if apiError != nil {
return nil, apiError
}
// Unmarshal the response
data := ProrationPreviewResponse{}
if err := json.Unmarshal(resp, &data); err != nil {
return nil, &APIError{
Error: fmt.Sprintf("Error unmarshaling response: %v", err),
Success: false,
}
}
// Return the response
return &data, nil
}
func (api *PrimeMonitorApi) EnterpriseLicenseActivate(payload EnterpriseLicenseActivatePayload) (*EnterpriseLicenseActivateResponse, *APIError) {
resp, apiError := api.post(api.host+ENTERPRISE_LICENSE_ACTIVATE, payload)
if apiError != nil {
return nil, apiError
}
data := EnterpriseLicenseActivateResponse{}
if err := json.Unmarshal(resp, &data); err != nil {
return nil, &APIError{
Error: fmt.Sprintf("Error unmarshaling response: %v", err),
Success: false,
}
}
return &data, nil
}
func (api *PrimeMonitorApi) SyncEnterpriseLicense(payload EnterpriseLicenseSyncPayload) (*EnterpriseLicenseActivateResponse, *APIError) {
resp, apiError := api.post(api.host+SYNC_ENTERPRISE_LICENSE, payload)
if apiError != nil {
return nil, apiError
}
data := EnterpriseLicenseActivateResponse{}
if err := json.Unmarshal(resp, &data); err != nil {
return nil, &APIError{
Error: fmt.Sprintf("Error unmarshaling response: %v", err),
Success: false,
}
}
return &data, nil
}
func (api *PrimeMonitorApi) GetEnterpriseFeatureFlags(licenseKey string) (*FlagDataResponse, *APIError) {
resp, apiError := api.post(api.host+FEATURE_FLAGS, map[string]string{
"license_key": licenseKey,
"version": api.version,
})
if apiError != nil {
return nil, apiError
}
data := FlagDataResponse{}
if err := json.Unmarshal(resp, &data); err != nil {
return nil, &APIError{
Error: fmt.Sprintf("Error unmarshaling response: %v", err),
Success: false,
}
}
return &data, nil
}
func (api *PrimeMonitorApi) DeactivateEnterpriseLicense(payload LicenseDeactivatePayload) *APIError {
_, apiError := api.post(api.host+DEACTIVATE_ENTERPRISE_LICENSE, payload)
if apiError != nil {
return apiError
}
return nil
}
// ====================== Enterprise License ==========================
func (api *PrimeMonitorApi) UpdateEnterpriseLicenseSeats(payload UpdateEnterpriseLicenseSeatsPayload) (*SeatUpdateResponse, *APIError) {
resp, apiError := api.post(api.host+UPDATE_ENTERPRISE_LICENSE_SEATS, payload)
if apiError != nil {
return nil, apiError
}
data := SeatUpdateResponse{}
if err := json.Unmarshal(resp, &data); err != nil {
return nil, &APIError{
Error: fmt.Sprintf("Error unmarshaling response: %v", err),
Success: false,
}
}
return &data, nil
}
func (api *PrimeMonitorApi) GetEnterpriseLicensePortal() (*EnterpriseLicensePortalResponse, *APIError) {
resp, apiError := api.post(api.host+ENTERPRISE_SUBSCRIPTION_PORTAL, nil)
if apiError != nil {
return nil, apiError
}
data := EnterpriseLicensePortalResponse{}
if err := json.Unmarshal(resp, &data); err != nil {
return nil, &APIError{
Error: fmt.Sprintf("Error unmarshaling response: %v", err),
Success: false,
}
}
return &data, nil
}
func (api *PrimeMonitorApi) GetEnterpriseLicenseProrationPreview(payload UpdateEnterpriseLicenseSeatsPayload) (*ProrationPreviewResponse, *APIError) {
resp, apiError := api.post(api.host+ENTERPRISE_PRORATION_PREVIEW, payload)
if apiError != nil {
return nil, apiError
}
data := ProrationPreviewResponse{}
if err := json.Unmarshal(resp, &data); err != nil {
return nil, &APIError{
Error: fmt.Sprintf("Error unmarshaling response: %v", err),
Success: false,
}
}
return &data, nil
}
// ------------------------ Helper Methods ----------------------------------
/*
prepareRequest prepares an HTTP request with the necessary headers and parameters.
Parameters:
- method: string specifying the HTTP method (e.g., "GET", "POST").
- urlStr: string specifying the URL for the request.
- body: io.Reader containing the request body.
- params: map[string]string containing the query parameters.
Returns:
- *http.Request: The prepared HTTP request.
- error: An error if any occurs during the preparation.
*/
func (api *PrimeMonitorApi) prepareRequest(method, urlStr string, body io.Reader, params map[string]string) (*http.Request, error) {
if method == "GET" && params != nil {
parsedURL, err := url.Parse(urlStr)
if err != nil {
return nil, fmt.Errorf("error parsing URL: %v", err)
}
query := parsedURL.Query()
for key, value := range params {
query.Set(key, value)
}
parsedURL.RawQuery = query.Encode()
urlStr = parsedURL.String()
}
req, err := http.NewRequest(method, urlStr, body)
if err != nil {
return nil, fmt.Errorf("error creating request: %v", err)
}
headers := map[string]string{
"X-Instance-Id": api.instanceId,
"X-Machine-Signature": api.machineSignature,
"X-Client": api.client,
"X-License-Version": api.version,
"Content-Type": "application/json",
}
for key, value := range headers {
req.Header.Add(key, value)
}
return req, nil
}
/*
doRequest executes the HTTP request and handles common response scenarios.
Parameters:
- req: *http.Request specifying the HTTP request to execute.
Returns:
- []byte: The response body.
- error: An error if any occurs during the request execution.
*/
func (api *PrimeMonitorApi) doRequest(req *http.Request) ([]byte, *APIError) {
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, &APIError{
Error: fmt.Sprintf("error making request: %v", err),
Success: false,
}
}
defer resp.Body.Close()
switch {
case resp.StatusCode >= 200 && resp.StatusCode <= 227:
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, &APIError{
Error: fmt.Sprintf("error reading response body: %v", err),
Success: false,
}
}
return body, nil
case resp.StatusCode >= 300 && resp.StatusCode <= 308:
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, &APIError{
Error: fmt.Sprintf("error reading response body: %v", err),
Success: false,
}
}
return body, nil
case resp.StatusCode >= 400 && resp.StatusCode <= 451:
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, &APIError{
Error: fmt.Sprintf("error reading response body: %v", err),
Success: false,
}
}
// Try to parse the error response
var errorResp ErrorResponse
if err := json.Unmarshal(body, &errorResp); err != nil {
// If we can't parse the error, return the status code
return nil, &APIError{
Error: fmt.Sprintf("unexpected status code %d", resp.StatusCode),
Success: false,
}
}
return nil, &APIError{
Error: errorResp.Error,
Success: false,
}
default:
return nil, &APIError{
Error: fmt.Sprintf("unexpected status code: %v", resp.StatusCode),
Success: false,
}
}
}
/*
get performs a GET request.
Parameters:
- baseURL: string specifying the base URL for the request.
- params: map[string]string containing the query parameters.
Returns:
- []byte: The response body.
- error: An error if any occurs during the request.
*/
func (api *PrimeMonitorApi) get(baseURL string, params map[string]string) ([]byte, *APIError) {
req, err := api.prepareRequest("GET", baseURL, nil, params)
if err != nil {
return nil, &APIError{
Error: fmt.Sprintf("error preparing request: %v", err),
Success: false,
}
}
return api.doRequest(req)
}
/*
post performs a POST request with JSON body.
Parameters:
- baseURL: string specifying the base URL for the request.
- data: interface{} containing the data to be sent in the request body.
Returns:
- []byte: The response body.
- error: An error if any occurs during the request.
*/
func (api *PrimeMonitorApi) post(baseURL string, data interface{}) ([]byte, *APIError) {
jsonData, err := json.Marshal(data)
if err != nil {
return nil, &APIError{
Error: fmt.Sprintf("error marshaling data: %v", err),
Success: false,
}
}
req, err := api.prepareRequest("POST", baseURL, bytes.NewBuffer(jsonData), nil)
if err != nil {
return nil, &APIError{
Error: fmt.Sprintf("error preparing request: %v", err),
Success: false,
}
}
return api.doRequest(req)
}