Skip to content

Commit

Permalink
feat: adds support for vanity subdomains
Browse files Browse the repository at this point in the history
Co-authored-by: Han Qiao <[email protected]>
  • Loading branch information
darora and sweatybridge committed Dec 9, 2022
1 parent f646b19 commit 76fbeee
Show file tree
Hide file tree
Showing 9 changed files with 961 additions and 7 deletions.
151 changes: 149 additions & 2 deletions api/beta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,119 @@ paths:
description: Failed to update project network restrictions
tags: *ref_18
security: *ref_19
/v1/projects/{ref}/vanity-subdomain:
get:
operationId: getVanitySubdomainConfig
summary: Gets current vanity subdomain config
parameters:
- name: ref
required: true
in: path
description: Project ref
schema:
minLength: 20
maxLength: 20
type: string
responses:
'200':
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/VanitySubdomainConfigResponse'
'403':
description: ''
'500':
description: Failed to get project vanity subdomain configuration
tags: &ref_20
- vanity subdomain (beta)
security: &ref_21
- bearer: []
delete:
operationId: removeVanitySubdomainConfig
summary: Deletes a project's vanity subdomain configuration
parameters:
- name: ref
required: true
in: path
description: Project ref
schema:
minLength: 20
maxLength: 20
type: string
responses:
'200':
description: ''
'403':
description: ''
'500':
description: Failed to delete project vanity subdomain configuration
tags: *ref_20
security: *ref_21
/v1/projects/{ref}/vanity-subdomain/check-availability:
post:
operationId: checkVanitySubdomainAvailability
summary: Checks vanity subdomain availability
parameters:
- name: ref
required: true
in: path
description: Project ref
schema:
minLength: 20
maxLength: 20
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/VanitySubdomainBody'
responses:
'201':
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/SubdomainAvailabilityResponse'
'403':
description: ''
'500':
description: Failed to check project vanity subdomain configuration
tags: *ref_20
security: *ref_21
/v1/projects/{ref}/vanity-subdomain/activate:
post:
operationId: activateVanitySubdomainPlease
summary: Activates a vanity subdomain for a project.
parameters:
- name: ref
required: true
in: path
description: Project ref
schema:
minLength: 20
maxLength: 20
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/VanitySubdomainBody'
responses:
'201':
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/ActivateVanitySubdomainResponse'
'403':
description: ''
'500':
description: Failed to activate project vanity subdomain configuration
tags: *ref_20
security: *ref_21
info:
title: Supabase API (v1)
description: ''
Expand Down Expand Up @@ -904,7 +1017,7 @@ components:
name:
type: string
status:
enum: &ref_20
enum: &ref_22
- ACTIVE
- REMOVED
- THROTTLED
Expand Down Expand Up @@ -937,7 +1050,7 @@ components:
name:
type: string
status:
enum: *ref_20
enum: *ref_22
type: string
version:
type: number
Expand Down Expand Up @@ -1075,3 +1188,37 @@ components:
- entitlement
- config
- status
VanitySubdomainConfigResponse:
type: object
properties:
status:
enum:
- not-used
- custom-domain-used
- active
type: string
custom_domain:
type: string
required:
- status
VanitySubdomainBody:
type: object
properties:
vanity_subdomain:
type: string
required:
- vanity_subdomain
SubdomainAvailabilityResponse:
type: object
properties:
available:
type: boolean
required:
- available
ActivateVanitySubdomainResponse:
type: object
properties:
custom_domain:
type: string
required:
- custom_domain
14 changes: 9 additions & 5 deletions cmd/domains.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ var (
GroupID: groupManagementAPI,
Use: "domains",
Short: "Manage custom domain names for Supabase projects",
Long: `Manage custom domain names for Supabase projects.
Use of custom domains and vanity subdomains is mutually exclusive.
`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
if !experimental {
return errors.New("must set the --experimental flag to run this command")
Expand All @@ -32,7 +36,7 @@ var (

customHostnamesCreateCmd = &cobra.Command{
Use: "create",
Short: "Create a custom hostname.",
Short: "Create a custom hostname",
Long: `Create a custom hostname for your Supabase project.
Expects your custom hostname to have a CNAME record to your Supabase project's subdomain.`,
Expand All @@ -48,7 +52,7 @@ Expects your custom hostname to have a CNAME record to your Supabase project's s

customHostnamesGetCmd = &cobra.Command{
Use: "get",
Short: "Get the current custom hostname config.",
Short: "Get the current custom hostname config",
Long: "Retrieve the custom hostname config for your project, as stored in the Supabase platform.",
RunE: func(cmd *cobra.Command, args []string) error {
fsys := afero.NewOsFs()
Expand All @@ -62,7 +66,7 @@ Expects your custom hostname to have a CNAME record to your Supabase project's s

customHostnamesReverifyCmd = &cobra.Command{
Use: "reverify",
Short: "Re-verify the custom hostname config for your project.",
Short: "Re-verify the custom hostname config for your project",
RunE: func(cmd *cobra.Command, args []string) error {
ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
return reverify.Run(ctx, projectRef, rawOutput, afero.NewOsFs())
Expand All @@ -71,7 +75,7 @@ Expects your custom hostname to have a CNAME record to your Supabase project's s

customHostnamesActivateCmd = &cobra.Command{
Use: "activate",
Short: "Activate the custom hostname for a project.",
Short: "Activate the custom hostname for a project",
Long: `Activates the custom hostname configuration for a project.
This reconfigures your Supabase project to respond to requests on your custom hostname.
Expand All @@ -84,7 +88,7 @@ After the custom hostname is activated, your project's auth services will no lon

customHostnamesDeleteCmd = &cobra.Command{
Use: "delete",
Short: "Deletes the custom hostname config for your project.",
Short: "Deletes the custom hostname config for your project",
RunE: func(cmd *cobra.Command, args []string) error {
ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
return delete.Run(ctx, projectRef, afero.NewOsFs())
Expand Down
95 changes: 95 additions & 0 deletions cmd/vanitySubdomains.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package cmd

import (
"errors"
"os"
"os/signal"

"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/supabase/cli/internal/vanity_subdomains/activate"
"github.com/supabase/cli/internal/vanity_subdomains/check"
"github.com/supabase/cli/internal/vanity_subdomains/delete"
"github.com/supabase/cli/internal/vanity_subdomains/get"
)

var (
vanityCmd = &cobra.Command{
GroupID: groupManagementAPI,
Use: "vanity-subdomains",
Short: "Manage vanity subdomains for Supabase projects",
Long: `Manage vanity subdomains for Supabase projects.
Usage of vanity subdomains and custom domains is mutually exclusive.`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
if !experimental {
return errors.New("must set the --experimental flag to run this command")
}
return cmd.Root().PersistentPreRunE(cmd, args)
},
}

desiredSubdomain string

vanityActivateCmd = &cobra.Command{
Use: "activate",
Short: "Activate a vanity subdomain",
Long: `Activate a vanity subdomain for your Supabase project.
This reconfigures your Supabase project to respond to requests on your vanity subdomain.
After the vanity subdomain is activated, your project's auth services will no longer function on the {project-ref}.{supabase-domain} hostname.
`,
RunE: func(cmd *cobra.Command, args []string) error {
fsys := afero.NewOsFs()
if err := PromptLogin(fsys); err != nil {
return err
}
ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
return activate.Run(ctx, projectRef, desiredSubdomain, fsys)
},
}

vanityGetCmd = &cobra.Command{
Use: "get",
Short: "Get the current vanity subdomain",
RunE: func(cmd *cobra.Command, args []string) error {
fsys := afero.NewOsFs()
if err := PromptLogin(fsys); err != nil {
return err
}
ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
return get.Run(ctx, projectRef, fsys)
},
}

vanityCheckCmd = &cobra.Command{
Use: "check-availability",
Short: "Checks if a desired subdomain is available for use",
RunE: func(cmd *cobra.Command, args []string) error {
ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
return check.Run(ctx, projectRef, desiredSubdomain, afero.NewOsFs())
},
}

vanityDeleteCmd = &cobra.Command{
Use: "delete",
Short: "Deletes a project's vanity subdomain",
Long: `Deletes the vanity subdomain for a project, and reverts to using the project ref for routing.`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
return delete.Run(ctx, projectRef, afero.NewOsFs())
},
}
)

func init() {
vanityCmd.PersistentFlags().StringVar(&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.")
vanityCheckCmd.Flags().StringVar(&desiredSubdomain, "desired-subdomain", "", "The desired vanity subdomain to use for your Supabase project.")
vanityCmd.AddCommand(vanityGetCmd)
vanityCmd.AddCommand(vanityCheckCmd)
vanityCmd.AddCommand(vanityActivateCmd)
vanityCmd.AddCommand(vanityDeleteCmd)

rootCmd.AddCommand(vanityCmd)
}
47 changes: 47 additions & 0 deletions internal/vanity_subdomains/activate/activate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package activate

import (
"context"
"errors"
"fmt"
"strings"

"github.com/spf13/afero"
"github.com/supabase/cli/internal/utils"
"github.com/supabase/cli/pkg/api"
)

func Run(ctx context.Context, projectRefArg string, desiredSubdomain string, fsys afero.Fs) error {
// 1. Sanity checks.
projectRef := projectRefArg
subdomain := strings.TrimSpace(desiredSubdomain)
{
if len(projectRefArg) == 0 {
ref, err := utils.LoadProjectRef(fsys)
if err != nil {
return err
}
projectRef = ref
} else if !utils.ProjectRefPattern.MatchString(projectRef) {
return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
}
if len(subdomain) == 0 {
return errors.New("non-empty vanity subdomain expected")
}
}

// 2. create vanity subdomain
{
resp, err := utils.GetSupabase().ActivateVanitySubdomainPleaseWithResponse(ctx, projectRef, api.ActivateVanitySubdomainPleaseJSONRequestBody{
VanitySubdomain: subdomain,
})
if err != nil {
return 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
}
}
Loading

0 comments on commit 76fbeee

Please sign in to comment.