-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: world forge organization and project
- Loading branch information
Showing
14 changed files
with
2,748 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
package forge | ||
|
||
import ( | ||
"bufio" | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"os" | ||
"os/exec" | ||
"runtime" | ||
"strings" | ||
|
||
"github.com/google/uuid" | ||
"github.com/rotisserie/eris" | ||
"github.com/tidwall/gjson" | ||
|
||
"pkg.world.dev/world-cli/common/globalconfig" | ||
) | ||
|
||
// sendRequest sends an HTTP request with auth token and returns the response body | ||
func sendRequest(ctx context.Context, method, url string, body interface{}) ([]byte, error) { | ||
var bodyReader io.Reader | ||
|
||
if body != nil { | ||
jsonBody, err := json.Marshal(body) | ||
if err != nil { | ||
return nil, eris.Wrap(err, "Failed to marshal request body") | ||
} | ||
bodyReader = bytes.NewReader(jsonBody) | ||
} | ||
|
||
// Get credential from config | ||
cred, err := globalconfig.GetGlobalConfig() | ||
if err != nil { | ||
return nil, eris.Wrap(err, "Failed to get credential") | ||
} | ||
|
||
// Create request | ||
req, err := http.NewRequestWithContext(ctx, method, url, bodyReader) | ||
if err != nil { | ||
return nil, eris.Wrap(err, "Failed to create request") | ||
} | ||
|
||
// Add authorization header | ||
req.Header.Add("Authorization", "Bearer "+cred.Credential.Token) | ||
|
||
// Add content-type header for requests with body | ||
if body != nil { | ||
req.Header.Set("Content-Type", "application/json") | ||
} | ||
|
||
// Make request | ||
client := &http.Client{} | ||
resp, err := client.Do(req) | ||
if err != nil { | ||
return nil, eris.Wrap(err, "Failed to make request") | ||
} | ||
defer resp.Body.Close() | ||
|
||
// Check status code | ||
if resp.StatusCode != http.StatusOK { | ||
return nil, eris.Errorf("Unexpected status code: %d", resp.StatusCode) | ||
} | ||
|
||
// Read response body | ||
respBody, err := io.ReadAll(resp.Body) | ||
if err != nil { | ||
return nil, eris.Wrap(err, "Failed to read response body") | ||
} | ||
|
||
return respBody, nil | ||
} | ||
|
||
func parseResponse[T any](body []byte) (*T, error) { | ||
result := gjson.GetBytes(body, "data") | ||
if !result.Exists() { | ||
return nil, eris.New("Missing data field in response") | ||
} | ||
|
||
var data T | ||
if err := json.Unmarshal([]byte(result.Raw), &data); err != nil { | ||
return nil, eris.Wrap(err, "Failed to parse response") | ||
} | ||
|
||
return &data, nil | ||
} | ||
|
||
func printNoSelectedOrganization() { | ||
fmt.Println("You don't have any organization selected.") | ||
fmt.Println("Use 'world forge organization switch' to select one.") | ||
fmt.Println() | ||
} | ||
|
||
func printNoSelectedProject() { | ||
fmt.Println("You don't have any project selected.") | ||
fmt.Println("Use 'world forge project switch' to select one.") | ||
fmt.Println() | ||
} | ||
|
||
func printNoProjectsInOrganization() { | ||
fmt.Println("You don't have any projects in this organization yet.") | ||
fmt.Println("Use 'world forge project create' to create one.") | ||
fmt.Println() | ||
} | ||
|
||
func isAlphanumeric(s string) bool { | ||
for _, r := range s { | ||
if !((r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9')) { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
var generateKey = func() string { | ||
return strings.ReplaceAll(uuid.NewString(), "-", "") | ||
} | ||
|
||
// Change from function to variable | ||
var openBrowser = func(url string) error { | ||
var err error | ||
switch runtime.GOOS { | ||
case "linux": | ||
err = exec.Command("xdg-open", url).Start() | ||
case "windows": | ||
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() | ||
case "darwin": | ||
err = exec.Command("open", url).Start() | ||
default: | ||
err = eris.New("unsupported platform") | ||
} | ||
if err != nil { | ||
return eris.Wrap(err, "failed to open browser") | ||
} | ||
return nil | ||
} | ||
|
||
var getInput = func() (string, error) { | ||
reader := bufio.NewReader(os.Stdin) | ||
input, err := reader.ReadString('\n') | ||
if err != nil { | ||
return "", eris.Wrap(err, "Failed to read input") | ||
} | ||
return strings.TrimSpace(input), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
package forge | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
"strings" | ||
|
||
"github.com/rotisserie/eris" | ||
|
||
"pkg.world.dev/world-cli/common/globalconfig" | ||
) | ||
|
||
// Deploy a project | ||
func deploy(ctx context.Context) error { | ||
globalConfig, err := globalconfig.GetGlobalConfig() | ||
if err != nil { | ||
return eris.Wrap(err, "Failed to get global config") | ||
} | ||
|
||
projectID := globalConfig.ProjectID | ||
organizationID := globalConfig.OrganizationID | ||
|
||
if organizationID == "" { | ||
printNoSelectedOrganization() | ||
return nil | ||
} | ||
|
||
if projectID == "" { | ||
printNoSelectedProject() | ||
return nil | ||
} | ||
|
||
// Get organization details | ||
org, err := getSelectedOrganization(ctx) | ||
if err != nil { | ||
return eris.Wrap(err, "Failed to get organization details") | ||
} | ||
|
||
// Get project details | ||
prj, err := getSelectedProject(ctx) | ||
if err != nil { | ||
return eris.Wrap(err, "Failed to get project details") | ||
} | ||
|
||
fmt.Println("Deployment Details") | ||
fmt.Println("-----------------") | ||
fmt.Printf("Organization: %s\n", org.Name) | ||
fmt.Printf("Org Slug: %s\n", org.Slug) | ||
fmt.Printf("Project: %s\n", prj.Name) | ||
fmt.Printf("Project Slug: %s\n", prj.Slug) | ||
fmt.Printf("Repository: %s\n\n", prj.RepoURL) | ||
|
||
deployURL := fmt.Sprintf("%s/api/organization/%s/project/%s/deploy", baseURL, organizationID, projectID) | ||
_, err = sendRequest(ctx, http.MethodPost, deployURL, nil) | ||
if err != nil { | ||
return eris.Wrap(err, "Failed to deploy project") | ||
} | ||
|
||
fmt.Println("\n✨ Your deployment is being processed! ✨") | ||
fmt.Println("\nTo check the status of your deployment, run:") | ||
fmt.Println(" $ 'world forge deployment status'") | ||
|
||
return nil | ||
} | ||
|
||
// Destroy a project | ||
func destroy(ctx context.Context) error { | ||
globalConfig, err := globalconfig.GetGlobalConfig() | ||
if err != nil { | ||
return eris.Wrap(err, "Failed to get global config") | ||
} | ||
|
||
projectID := globalConfig.ProjectID | ||
organizationID := globalConfig.OrganizationID | ||
|
||
if organizationID == "" { | ||
printNoSelectedOrganization() | ||
return nil | ||
} | ||
|
||
if projectID == "" { | ||
printNoSelectedProject() | ||
return nil | ||
} | ||
|
||
// Get organization details | ||
org, err := getSelectedOrganization(ctx) | ||
if err != nil { | ||
return eris.Wrap(err, "Failed to get organization details") | ||
} | ||
|
||
// Get project details | ||
prj, err := getSelectedProject(ctx) | ||
if err != nil { | ||
return eris.Wrap(err, "Failed to get project details") | ||
} | ||
|
||
fmt.Println("Project Details") | ||
fmt.Println("-----------------") | ||
fmt.Printf("Organization: %s\n", org.Name) | ||
fmt.Printf("Org Slug: %s\n", org.Slug) | ||
fmt.Printf("Project: %s\n", prj.Name) | ||
fmt.Printf("Project Slug: %s\n", prj.Slug) | ||
fmt.Printf("Repository: %s\n\n", prj.RepoURL) | ||
|
||
fmt.Print("Are you sure you want to destroy this project? (y/N): ") | ||
response, err := getInput() | ||
if err != nil { | ||
return eris.Wrap(err, "Failed to read response") | ||
} | ||
|
||
response = strings.ToLower(strings.TrimSpace(response)) | ||
if response != "y" { | ||
fmt.Println("Destroy cancelled") | ||
return nil | ||
} | ||
|
||
destroyURL := fmt.Sprintf("%s/api/organization/%s/project/%s/destroy", baseURL, organizationID, projectID) | ||
_, err = sendRequest(ctx, http.MethodPost, destroyURL, nil) | ||
if err != nil { | ||
return eris.Wrap(err, "Failed to destroy project") | ||
} | ||
|
||
fmt.Println("\n🗑️ Your destroy request is being processed!") | ||
fmt.Println("\nTo check the status of your destroy request, run:") | ||
fmt.Println(" $ 'world forge deployment status'") | ||
|
||
return nil | ||
} |
Oops, something went wrong.