mirror of
https://github.com/makeplane/plane.git
synced 2026-07-12 21:40:18 +02:00
* feat: added feature flag package for decrypting and refreshing the feature flags value * feat: added feature flagging route for route handlers * feat: added feature flag worker for managing and distributing feature flags * feat: implemented feature flag api implementation inside ff handler * feat: added exponential retry interval inside feature flagging * fix: route pattern of feature flag endpoint from monitor * feat: created endpoint to activate workspace from monitor * feat: made changes to accomodate db and activate endpoint * dev: update multi tenant environment settings * dev: workspace license activation flow * dev: update license activate endpoint * dev: update workspace license activate endpoint * feat: added feature flag user sync logic * feat: added constants and sync logic with private key inside * feat: added port as env variable * feat: added modification for removing license key in healthcheck * feat: example env pushed * fix: update monitor endpoints and license activation function * dev: initiate free workspace only for self hosted instances * dev: self hosting license endpoint updates * dev: remove monitor db * dev: update handlers * dev: self hosted license subscription web * dev: removed computed in self hosted subscription * fix: prime server url and delete old license while creating new license in sqlite * chore: subscription activation alert after successfull activation. * dev: update feature flag endpoints * fix: handled several cases of feature flags * fix: linting errors * dev: update monitor to handle workspace activation * dev: update decryption algorithm * feat: separated activation payload with the sync payload * dev: update feature flags endpoint * dev: change type for workspace sync * feat: added isActive flag for user activation * feat: removed creation of free users in monitor * feat: added method for workspace product * dev: add user email in types * feat: added method for workspace product * fix: uer license feature flag fetch * fix: handler fixed for feature key * chore: self managed license activation * chore: update validation license flow. * chore: license activation success. * dev: resync workspace license on activation * chore: plane one success modal. * feat: added constructs to inject private key build time * feat: updated the docker file with private key build argument * feat: added consumption od DEFAULT_PRIVATE_KEY when building monitor * dev: update private key --------- Co-authored-by: Henit Chobisa <chobisa.henit@gmail.com> Co-authored-by: gurusainath <gurusainath007@gmail.com> Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com> Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
285 lines
7.9 KiB
Go
285 lines
7.9 KiB
Go
package prime_api
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
)
|
|
|
|
type IPrimeMonitorApi interface {
|
|
PostServiceStatus(StatusPayload) ErrorCode
|
|
GetFeatureFlags(licenseKey string) (*FlagDataResponse, ErrorCode)
|
|
ActivateInstance() ErrorCode
|
|
DeactivateInstance() ErrorCode
|
|
SyncWorkspace(WorkspaceSyncPayload) (*WorkspaceActivationResponse, ErrorCode)
|
|
ActivateWorkspace(WorkspaceActivationPayload) (*WorkspaceActivationResponse, ErrorCode)
|
|
ActivateFreeWorkspace(WorkspaceActivationPayload) (*WorkspaceActivationResponse, ErrorCode)
|
|
}
|
|
|
|
type PrimeMonitorApi struct {
|
|
host string
|
|
instanceId string
|
|
appVersion string
|
|
client string
|
|
version string
|
|
machineSignature string
|
|
}
|
|
|
|
type FlagsPayload struct {
|
|
EncryptedData string `json:"encrypted_data"`
|
|
}
|
|
|
|
func NewMonitorApi(host, machineSignature, instanceId, appVersion string) IPrimeMonitorApi {
|
|
return &PrimeMonitorApi{
|
|
host: host,
|
|
instanceId: instanceId,
|
|
appVersion: appVersion,
|
|
client: "Prime-Monitor",
|
|
version: appVersion,
|
|
machineSignature: machineSignature,
|
|
}
|
|
}
|
|
|
|
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/"
|
|
)
|
|
|
|
/* ----------------------- 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) ActivateFreeWorkspace(payload WorkspaceActivationPayload) (*WorkspaceActivationResponse, ErrorCode) {
|
|
resp, err := api.post(api.host+FREE_WORKSPACE_ACTIVATE, payload)
|
|
if err != nil {
|
|
return nil, UNABLE_TO_ACTIVATE_WORKSPACE
|
|
}
|
|
data := WorkspaceActivationResponse{}
|
|
err = json.Unmarshal(resp, &data)
|
|
if err != nil {
|
|
return nil, UNABLE_TO_ACTIVATE_WORKSPACE
|
|
}
|
|
return &data, 0
|
|
}
|
|
|
|
// Activating a paid licensed workspace
|
|
func (api *PrimeMonitorApi) ActivateWorkspace(payload WorkspaceActivationPayload) (*WorkspaceActivationResponse, ErrorCode) {
|
|
resp, err := api.post(api.host+WORKSPACE_ACTIVATE, payload)
|
|
if err != nil {
|
|
return nil, UNABLE_TO_ACTIVATE_WORKSPACE
|
|
}
|
|
data := WorkspaceActivationResponse{}
|
|
err = json.Unmarshal(resp, &data)
|
|
if err != nil {
|
|
return nil, UNABLE_TO_ACTIVATE_WORKSPACE
|
|
}
|
|
return &data, 0
|
|
}
|
|
|
|
func (api *PrimeMonitorApi) GetFeatureFlags(licenseKey string) (*FlagDataResponse, ErrorCode) {
|
|
flagData, err := api.post(api.host+FEATURE_FLAGS, map[string]string{
|
|
"license_key": licenseKey,
|
|
"version": api.version,
|
|
})
|
|
if err != nil {
|
|
return nil, UNABLE_TO_GET_FEATURE_FLAGS
|
|
}
|
|
data := FlagDataResponse{}
|
|
err = json.Unmarshal(flagData, &data)
|
|
if err != nil {
|
|
return nil, UNABLE_TO_PARSE_FEATURE_FLAGS
|
|
}
|
|
return &data, 0
|
|
}
|
|
|
|
func (api *PrimeMonitorApi) SyncWorkspace(payload WorkspaceSyncPayload) (*WorkspaceActivationResponse, ErrorCode) {
|
|
resp, err := api.post(api.host+SYNC_WORKSPACE, payload)
|
|
if err != nil {
|
|
return nil, UNABLE_TO_SYNC_WORKSPACE
|
|
}
|
|
data := WorkspaceActivationResponse{}
|
|
err = json.Unmarshal(resp, &data)
|
|
|
|
if err != nil {
|
|
return nil, UNABLE_TO_SYNC_WORKSPACE
|
|
}
|
|
return &data, 0
|
|
}
|
|
|
|
func (api *PrimeMonitorApi) ActivateInstance() ErrorCode {
|
|
_, err := api.post(api.host+ACTIVATE_INSTANCE, nil)
|
|
if err != nil {
|
|
return UNABLE_TO_ACTIVATE_WORKSPACE
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (api *PrimeMonitorApi) DeactivateInstance() ErrorCode {
|
|
_, err := api.post(api.host+DEACTIVATE_INSTANCE, nil)
|
|
if err != nil {
|
|
return UNABLE_TO_ACTIVATE_WORKSPACE
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (api *PrimeMonitorApi) UpgradeInstance() ErrorCode {
|
|
_, err := api.post(api.host+UPGRADE_INSTANCE, nil)
|
|
if err != nil {
|
|
return UNABLE_TO_ACTIVATE_WORKSPACE
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// ------------------------ 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, error) {
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error making request: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
switch {
|
|
case resp.StatusCode >= 200 && resp.StatusCode <= 227:
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error reading response body: %v", err)
|
|
}
|
|
return body, nil
|
|
case resp.StatusCode >= 300 && resp.StatusCode <= 308:
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error reading response body: %v", err)
|
|
}
|
|
return body, nil
|
|
case resp.StatusCode >= 400 && resp.StatusCode <= 451:
|
|
return nil, fmt.Errorf("unexpected status code %d", resp.StatusCode)
|
|
default:
|
|
return nil, fmt.Errorf("unexpected status code: %v", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
/*
|
|
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, error) {
|
|
req, err := api.prepareRequest("GET", baseURL, nil, params)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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, error) {
|
|
jsonData, err := json.Marshal(data)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error marshaling data: %v", err)
|
|
}
|
|
|
|
req, err := api.prepareRequest("POST", baseURL, bytes.NewBuffer(jsonData), nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return api.doRequest(req)
|
|
}
|