Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cmd/vanitySubdomains.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -64,11 +64,12 @@ After the vanity subdomain is activated, your project's auth services will no lo
func init() {
vanityCmd.PersistentFlags().StringVar(&flags.ProjectRef, "project-ref", "", "Project ref of the Supabase project.")
vanityActivateCmd.Flags().StringVar(&desiredSubdomain, "desired-subdomain", "", "The desired vanity subdomain to use for your Supabase project.")
cobra.CheckErr(vanityActivateCmd.MarkFlagRequired("desired-subdomain"))
vanityCheckCmd.Flags().StringVar(&desiredSubdomain, "desired-subdomain", "", "The desired vanity subdomain to use for your Supabase project.")
cobra.CheckErr(vanityCheckCmd.MarkFlagRequired("desired-subdomain"))
vanityCmd.AddCommand(vanityGetCmd)
vanityCmd.AddCommand(vanityCheckCmd)
vanityCmd.AddCommand(vanityActivateCmd)
vanityCmd.AddCommand(vanityDeleteCmd)

rootCmd.AddCommand(vanityCmd)
}
33 changes: 12 additions & 21 deletions internal/vanity_subdomains/activate/activate.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,7 @@ package activate
import (
"context"
"fmt"
"strings"
"os"

"github.com/go-errors/errors"
"github.com/spf13/afero"
Expand All@@ -12,26 +12,17 @@ import (
)

func Run(ctx context.Context, projectRef string, desiredSubdomain string, fsys afero.Fs) error {
// 1. Sanity checks.
subdomain := strings.TrimSpace(desiredSubdomain)
{
if len(subdomain) == 0 {
return errors.New("non-empty vanity subdomain expected")
}
resp, err := utils.GetSupabase().V1ActivateVanitySubdomainConfigWithResponse(ctx, projectRef, api.V1ActivateVanitySubdomainConfigJSONRequestBody{
VanitySubdomain: desiredSubdomain,
})
if err != nil {
return errors.Errorf("failed activate vanity subdomain: %w", err)
} else if resp.JSON201 == nil {
return errors.Errorf("unexpected activate vanity subdomain status %d: %s", resp.StatusCode(), string(resp.Body))
}

// 2. create vanity subdomain
{
resp, err := utils.GetSupabase().V1ActivateVanitySubdomainConfigWithResponse(ctx, projectRef, api.V1ActivateVanitySubdomainConfigJSONRequestBody{
VanitySubdomain: subdomain,
})
if err != nil {
return errors.Errorf("failed activate vanity subdomain: %w", err)
}
if resp.JSON201 == nil {
return errors.New("failed to create vanity subdomain config: " + string(resp.Body))
}
fmt.Printf("Activated vanity subdomain at %s\n", resp.JSON201.CustomDomain)
return nil
if utils.OutputFormat.Value != utils.OutputPretty {
return utils.EncodeOutput(utils.OutputFormat.Value, os.Stdout, *resp.JSON201)
}
fmt.Printf("Activated vanity subdomain at %s\n", resp.JSON201.CustomDomain)
return nil
}
76 changes: 76 additions & 0 deletions internal/vanity_subdomains/activate/activate_test.go
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
package activate

import (
"context"
"net/http"
"testing"

"github.com/go-errors/errors"
"github.com/h2non/gock"
"github.com/stretchr/testify/assert"
"github.com/supabase/cli/internal/testing/apitest"
"github.com/supabase/cli/internal/testing/fstest"
"github.com/supabase/cli/internal/utils"
"github.com/supabase/cli/internal/utils/flags"
"github.com/supabase/cli/pkg/api"
)

func TestActivateSubdomain(t *testing.T) {
t.Run("actives vanity subdomain", func(t *testing.T) {
t.Cleanup(fstest.MockStdout(t, "Activated vanity subdomain at example.com\n"))
t.Cleanup(apitest.MockPlatformAPI(t))
// Setup mock api
gock.New(utils.DefaultApiHost).
Post("v1/projects/" + flags.ProjectRef + "/vanity-subdomain").
Reply(http.StatusCreated).
JSON(api.ActivateVanitySubdomainResponse{
CustomDomain: "example.com",
})
// Run test
err := Run(context.Background(), flags.ProjectRef, "example.com", nil)
assert.NoError(t, err)
})

t.Run("encodes toml output", func(t *testing.T) {
utils.OutputFormat.Value = utils.OutputToml
t.Cleanup(func() { utils.OutputFormat.Value = utils.OutputPretty })
t.Cleanup(fstest.MockStdout(t, `CustomDomain = "example.com"
`))
t.Cleanup(apitest.MockPlatformAPI(t))
// Setup mock api
gock.New(utils.DefaultApiHost).
Post("v1/projects/" + flags.ProjectRef + "/vanity-subdomain").
Reply(http.StatusCreated).
JSON(api.ActivateVanitySubdomainResponse{
CustomDomain: "example.com",
})
// Run test
err := Run(context.Background(), flags.ProjectRef, "example.com", nil)
assert.NoError(t, err)
})

t.Run("throws error on network error", func(t *testing.T) {
errNetwork := errors.New("network error")
t.Cleanup(apitest.MockPlatformAPI(t))
// Setup mock api
gock.New(utils.DefaultApiHost).
Post("/v1/projects/" + flags.ProjectRef + "/vanity-subdomain").
ReplyError(errNetwork)
// Run test
err := Run(context.Background(), flags.ProjectRef, "example.com", nil)
assert.ErrorIs(t, err, errNetwork)
})

t.Run("throws error on service unavailable", func(t *testing.T) {
utils.OutputFormat.Value = utils.OutputEnv
t.Cleanup(func() { utils.OutputFormat.Value = utils.OutputPretty })
t.Cleanup(apitest.MockPlatformAPI(t))
// Setup mock api
gock.New(utils.DefaultApiHost).
Post("/v1/projects/" + flags.ProjectRef + "/vanity-subdomain").
Reply(http.StatusServiceUnavailable)
// Run test
err := Run(context.Background(), flags.ProjectRef, "example.com", nil)
assert.ErrorContains(t, err, "unexpected activate vanity subdomain status 503:")
})
}
33 changes: 12 additions & 21 deletions internal/vanity_subdomains/check/check.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,7 @@ package check
import (
"context"
"fmt"
"strings"
"os"

"github.com/go-errors/errors"
"github.com/spf13/afero"
Expand All@@ -12,26 +12,17 @@ import (
)

func Run(ctx context.Context, projectRef string, desiredSubdomain string, fsys afero.Fs) error {
// 1. Sanity checks.
subdomain := strings.TrimSpace(desiredSubdomain)
{
if len(subdomain) == 0 {
return errors.New("non-empty vanity subdomain expected")
}
resp, err := utils.GetSupabase().V1CheckVanitySubdomainAvailabilityWithResponse(ctx, projectRef, api.V1CheckVanitySubdomainAvailabilityJSONRequestBody{
VanitySubdomain: desiredSubdomain,
})
if err != nil {
return errors.Errorf("failed to check vanity subdomain: %w", err)
} else if resp.JSON201 == nil {
return errors.Errorf("unexpected check vanity subdomain status %d: %s", resp.StatusCode(), string(resp.Body))
}

// 2. check if the subdomain is available
{
resp, err := utils.GetSupabase().V1CheckVanitySubdomainAvailabilityWithResponse(ctx, projectRef, api.V1CheckVanitySubdomainAvailabilityJSONRequestBody{
VanitySubdomain: subdomain,
})
if err != nil {
return errors.Errorf("failed to check vanity subdomain: %w", err)
}
if resp.JSON201 == nil {
return errors.New("failed to check subdomain availability: " + string(resp.Body))
}
fmt.Printf("Subdomain %s available: %+v\n", subdomain, resp.JSON201.Available)
return nil
if utils.OutputFormat.Value != utils.OutputPretty {
return utils.EncodeOutput(utils.OutputFormat.Value, os.Stdout, *resp.JSON201)
}
fmt.Printf("Subdomain %s available: %+v\n", desiredSubdomain, resp.JSON201.Available)
return nil
}
73 changes: 73 additions & 0 deletions internal/vanity_subdomains/check/check_test.go
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
package check

import (
"context"
"net/http"
"testing"

"github.com/go-errors/errors"
"github.com/h2non/gock"
"github.com/stretchr/testify/assert"
"github.com/supabase/cli/internal/testing/apitest"
"github.com/supabase/cli/internal/testing/fstest"
"github.com/supabase/cli/internal/utils"
"github.com/supabase/cli/internal/utils/flags"
"github.com/supabase/cli/pkg/api"
)

func TestCheckSubdomain(t *testing.T) {
t.Run("checks subdomain availability", func(t *testing.T) {
t.Cleanup(fstest.MockStdout(t, "Subdomain example.com available: true\n"))
t.Cleanup(apitest.MockPlatformAPI(t))
// Setup mock api
gock.New(utils.DefaultApiHost).
Post("v1/projects/" + flags.ProjectRef + "/vanity-subdomain/check-availability").
Reply(http.StatusCreated).
JSON(api.SubdomainAvailabilityResponse{
Available: true,
})
// Run test
err := Run(context.Background(), flags.ProjectRef, "example.com", nil)
assert.NoError(t, err)
})

t.Run("encodes toml output", func(t *testing.T) {
utils.OutputFormat.Value = utils.OutputToml
t.Cleanup(func() { utils.OutputFormat.Value = utils.OutputPretty })
t.Cleanup(fstest.MockStdout(t, "Available = false\n"))
t.Cleanup(apitest.MockPlatformAPI(t))
// Setup mock api
gock.New(utils.DefaultApiHost).
Post("v1/projects/" + flags.ProjectRef + "/vanity-subdomain/check-availability").
Reply(http.StatusCreated).
JSON(api.SubdomainAvailabilityResponse{})
// Run test
err := Run(context.Background(), flags.ProjectRef, "example.com", nil)
assert.NoError(t, err)
})

t.Run("throws error on network error", func(t *testing.T) {
errNetwork := errors.New("network error")
t.Cleanup(apitest.MockPlatformAPI(t))
// Setup mock api
gock.New(utils.DefaultApiHost).
Post("/v1/projects/" + flags.ProjectRef + "/vanity-subdomain/check-availability").
ReplyError(errNetwork)
// Run test
err := Run(context.Background(), flags.ProjectRef, "example.com", nil)
assert.ErrorIs(t, err, errNetwork)
})

t.Run("throws error on service unavailable", func(t *testing.T) {
utils.OutputFormat.Value = utils.OutputEnv
t.Cleanup(func() { utils.OutputFormat.Value = utils.OutputPretty })
t.Cleanup(apitest.MockPlatformAPI(t))
// Setup mock api
gock.New(utils.DefaultApiHost).
Post("/v1/projects/" + flags.ProjectRef + "/vanity-subdomain/check-availability").
Reply(http.StatusServiceUnavailable)
// Run test
err := Run(context.Background(), flags.ProjectRef, "example.com", nil)
assert.ErrorContains(t, err, "unexpected check vanity subdomain status 503:")
})
}
21 changes: 9 additions & 12 deletions internal/vanity_subdomains/delete/delete.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,24 +3,21 @@ package delete
import (
"context"
"fmt"
"net/http"
"os"

"github.com/go-errors/errors"
"github.com/spf13/afero"
"github.com/supabase/cli/internal/utils"
)

func Run(ctx context.Context, projectRef string, fsys afero.Fs) error {
// 1. Sanity checks.
// 2. delete config
{
resp, err := utils.GetSupabase().V1DeactivateVanitySubdomainConfigWithResponse(ctx, projectRef)
if err != nil {
return errors.Errorf("failed to delete vanity subdomain: %w", err)
}
if resp.StatusCode() != 200 {
return errors.New("failed to delete vanity subdomain config; received: " + string(resp.Body))
}
fmt.Println("Deleted vanity subdomain successfully.")
return nil
resp, err := utils.GetSupabase().V1DeactivateVanitySubdomainConfigWithResponse(ctx, projectRef)
if err != nil {
return errors.Errorf("failed to delete vanity subdomain: %w", err)
} else if resp.StatusCode() != http.StatusOK {
return errors.Errorf("unexpected delete vanity subdomain status %d: %s", resp.StatusCode(), string(resp.Body))
}
fmt.Fprintln(os.Stderr, "Deleted vanity subdomain successfully.")
return nil
}
54 changes: 54 additions & 0 deletions internal/vanity_subdomains/delete/delete_test.go
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
package delete

import (
"context"
"errors"
"net/http"
"testing"

"github.com/h2non/gock"
"github.com/stretchr/testify/assert"
"github.com/supabase/cli/internal/testing/apitest"
"github.com/supabase/cli/internal/utils"
"github.com/supabase/cli/internal/utils/flags"
)

func TestDeleteSubdomain(t *testing.T) {
flags.ProjectRef = apitest.RandomProjectRef()

t.Run("deletes vanity subdomain", func(t *testing.T) {
t.Cleanup(apitest.MockPlatformAPI(t))
// Setup mock api
gock.New(utils.DefaultApiHost).
Delete("v1/projects/" + flags.ProjectRef + "/vanity-subdomain").
Reply(http.StatusOK)
// Run test
err := Run(context.Background(), flags.ProjectRef, nil)
assert.NoError(t, err)
})

t.Run("throws error on network error", func(t *testing.T) {
errNetwork := errors.New("network error")
t.Cleanup(apitest.MockPlatformAPI(t))
// Setup mock api
gock.New(utils.DefaultApiHost).
Delete("/v1/projects/" + flags.ProjectRef + "/vanity-subdomain").
ReplyError(errNetwork)
// Run test
err := Run(context.Background(), flags.ProjectRef, nil)
assert.ErrorIs(t, err, errNetwork)
})

t.Run("throws error on service unavailable", func(t *testing.T) {
utils.OutputFormat.Value = utils.OutputEnv
t.Cleanup(func() { utils.OutputFormat.Value = utils.OutputPretty })
t.Cleanup(apitest.MockPlatformAPI(t))
// Setup mock api
gock.New(utils.DefaultApiHost).
Delete("/v1/projects/" + flags.ProjectRef + "/vanity-subdomain").
Reply(http.StatusServiceUnavailable)
// Run test
err := Run(context.Background(), flags.ProjectRef, nil)
assert.ErrorContains(t, err, "unexpected delete vanity subdomain status 503:")
})
}
29 changes: 14 additions & 15 deletions internal/vanity_subdomains/get/get.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,27 +3,26 @@ package get
import (
"context"
"fmt"
"os"

"github.com/go-errors/errors"
"github.com/spf13/afero"
"github.com/supabase/cli/internal/utils"
)

func Run(ctx context.Context, projectRef string, fsys afero.Fs) error {
// 1. Sanity checks.
// 2. get vanity subdomain config
{
response, err := utils.GetSupabase().V1GetVanitySubdomainConfigWithResponse(ctx, projectRef)
if err != nil {
return errors.Errorf("failed to get vanity subdomain: %w", err)
}
if response.JSON200 == nil {
return errors.Errorf("failed to obtain vanity subdomain config: %+v", string(response.Body))
}
fmt.Printf("Status: %s\n", response.JSON200.Status)
if response.JSON200.CustomDomain != nil {
fmt.Printf("Vanity subdomain: %s\n", *response.JSON200.CustomDomain)
}
return nil
resp, err := utils.GetSupabase().V1GetVanitySubdomainConfigWithResponse(ctx, projectRef)
if err != nil {
return errors.Errorf("failed to get vanity subdomain: %w", err)
} else if resp.JSON200 == nil {
return errors.Errorf("unexpected vanity subdomain status %d: %s", resp.StatusCode(), string(resp.Body))
}
if utils.OutputFormat.Value != utils.OutputPretty {
return utils.EncodeOutput(utils.OutputFormat.Value, os.Stdout, *resp.JSON200)
}
fmt.Printf("Status: %s\n", resp.JSON200.Status)
if resp.JSON200.CustomDomain != nil {
fmt.Printf("Vanity subdomain: %s\n", *resp.JSON200.CustomDomain)
}
return nil
}
Loading