Skip to content

Commit

Permalink
HTTP routes update (#180)
Browse files Browse the repository at this point in the history
New route `PATCH /http-routes/{payload}/{index}` and corresponding CLI command: `http mod ...`
  • Loading branch information
nt0xa authored Nov 1, 2024
1 parent a4540fe commit 12c7fc8
Show file tree
Hide file tree
Showing 17 changed files with 652 additions and 15 deletions.
33 changes: 33 additions & 0 deletions internal/actions/completion.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package actions

import (
"fmt"
"slices"
"strings"

Expand Down Expand Up @@ -36,6 +37,38 @@ func completePayloadName(acts *Actions) completionFunc {
}
}

func completeHTTPRoute(acts *Actions) completionFunc {
return func(
cmd *cobra.Command,
args []string,
_ string,
) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveError
}

payload, err := cmd.Flags().GetString("payload")
if err != nil {
return nil, cobra.ShellCompDirectiveError
}

routes, err := (*acts).HTTPRoutesList(cmd.Context(), HTTPRoutesListParams{
PayloadName: payload,
})
if err != nil {
return nil, cobra.ShellCompDirectiveError
}

completions := make([]string, len(routes))

for i, r := range routes {
completions[i] = fmt.Sprintf("%d\t%s %s -> %d", r.Index, r.Method, r.Path, r.Code)
}

return completions, cobra.ShellCompDirectiveNoFileComp
}
}

func completeOne(list []string) completionFunc {
return func(
_ *cobra.Command,
Expand Down
146 changes: 146 additions & 0 deletions internal/actions/http_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ import (

const (
HTTPRoutesCreateResultID = "http-routes/create"
HTTPRoutesUpdateResultID = "http-routes/update"
HTTPRoutesDeleteResultID = "http-routes/delete"
HTTPRoutesClearResultID = "http-routes/clear"
HTTPRoutesListResultID = "http-routes/list"
)

type HTTPActions interface {
HTTPRoutesCreate(context.Context, HTTPRoutesCreateParams) (*HTTPRoutesCreateResult, errors.Error)
HTTPRoutesUpdate(context.Context, HTTPRoutesUpdateParams) (*HTTPRoutesUpdateResult, errors.Error)
HTTPRoutesDelete(context.Context, HTTPRoutesDeleteParams) (*HTTPRoutesDeleteResult, errors.Error)
HTTPRoutesClear(context.Context, HTTPRoutesClearParams) (HTTPRoutesClearResult, errors.Error)
HTTPRoutesList(context.Context, HTTPRoutesListParams) (HTTPRoutesListResult, errors.Error)
Expand Down Expand Up @@ -145,6 +147,150 @@ func HTTPRoutesCreateCommand(acts *Actions, p *HTTPRoutesCreateParams, local boo
}
}

//
// Update
//

type HTTPRoutesUpdateParams struct {
Payload string `err:"payload" path:"payload" json:"-"`
Index int64 `err:"index" path:"index" json:"-"`
Method *string `err:"method" json:"method,omitempty"`
Path *string `err:"path" json:"path,omitempty"`
Code *int `err:"code" json:"code,omitempty"`
Headers map[string][]string `err:"headers" json:"headers,omitempty"`
Body *string `err:"body" json:"body,omitempty"`
IsDynamic *bool `err:"isDynamic" json:"isDynamic,omitempty"`
}

func (p HTTPRoutesUpdateParams) Validate() error {
methods := []string{models.HTTPMethodAny}
methods = append(methods, models.HTTPMethods...)

return validation.ValidateStruct(&p,
validation.Field(&p.Payload, validation.Required),
validation.Field(&p.Method,
validation.When(p.Method != nil, valid.OneOf(methods, false))),
validation.Field(&p.Path,
validation.When(p.Path != nil, validation.Match(regexp.MustCompile("^/.*")).Error(`path must start with "/"`))),
)
}

type HTTPRoutesUpdateResult struct {
HTTPRoute
}

func (r HTTPRoutesUpdateResult) ResultID() string {
return HTTPRoutesUpdateResultID
}

func HTTPRoutesUpdateCommand(acts *Actions, p *HTTPRoutesUpdateParams, local bool) (*cobra.Command, PrepareCommandFunc) {
cmd := &cobra.Command{
Use: "mod INDEX",
Short: "Update HTTP route",
Args: oneArg("INDEX"),
ValidArgsFunction: completeHTTPRoute(acts),
}

var (
headers []string
file bool

method string
path string
code int
isDynamic bool
body string
)

methods := append([]string{models.HTTPMethodAny}, models.HTTPMethods...)

cmd.Flags().StringVarP(&p.Payload, "payload", "p", "", "Payload name")
cmd.Flags().StringVarP(&method, "method", "m", "GET",
fmt.Sprintf("Request method (one of %s)", quoteAndJoin(methods)))
cmd.Flags().StringVarP(&path, "path", "P", "/", "Request path")
cmd.Flags().StringArrayVarP(&headers, "header", "H", []string{}, "Response header")
cmd.Flags().IntVarP(&code, "code", "c", 200, "Response status code")
cmd.Flags().BoolVarP(&isDynamic, "dynamic", "d", false, "Interpret body and headers as templates")
cmd.Flags().StringVarP(&body, "body", "b", "", "Response body")

// Add file flag only for local client, i.e. terminal.
// Otherwise anyone will be able to read files from server using telegram client.
if local {
cmd.Flags().BoolVarP(&file, "file", "f", false, "Treat BODY as path to file")
}

_ = cmd.RegisterFlagCompletionFunc("payload", completePayloadName(acts))
_ = cmd.RegisterFlagCompletionFunc("method", completeOne(methods))

return cmd, func(cmd *cobra.Command, args []string) errors.Error {
// Index
i, err := strconv.ParseInt(args[0], 10, 64)
if err != nil {
return errors.Validationf("invalid integer value %q", args[0])
}
p.Index = i

// Method
if cmd.Flags().Changed("method") {
p.Method = &method
}

// Path
if cmd.Flags().Changed("path") {
p.Path = &path
}

// Headers
if cmd.Flags().Changed("header") {
hh := make(map[string][]string)
for _, header := range headers {
if !strings.Contains(header, ":") {
return errors.Validationf(`header %q must contain ":"`, header)
}
parts := strings.SplitN(header, ":", 2)
name, value := parts[0], strings.TrimLeft(parts[1], " ")

if h, ok := hh[name]; ok {
h = append(h, value)
} else {
hh[name] = []string{value}
}
}
p.Headers = hh
}

// Code
if cmd.Flags().Changed("code") {
p.Code = &code
}

// IsDynamic
if cmd.Flags().Changed("dynamic") {
p.IsDynamic = &isDynamic
}

// Body
if cmd.Flags().Changed("body") {
var bodyBytes []byte

if file {
b, err := ioutil.ReadFile(body)
if err != nil {
return errors.Validationf("fail to read file %q", body)
}
bodyBytes = b
} else {
bodyBytes = []byte(body)
}
bodyBase64 := base64.StdEncoding.EncodeToString(bodyBytes)

p.Body = &bodyBase64
}

return nil
}
}

//
// Delete
//
Expand Down
32 changes: 32 additions & 0 deletions internal/actions/mock/Actions.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

60 changes: 60 additions & 0 deletions internal/actionsdb/http_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,66 @@ func (act *dbactions) HTTPRoutesCreate(ctx context.Context, p actions.HTTPRoutes
return &actions.HTTPRoutesCreateResult{HTTPRoute: HTTPRoute(*rec, payload.Subdomain)}, nil
}

func (act *dbactions) HTTPRoutesUpdate(ctx context.Context, p actions.HTTPRoutesUpdateParams) (*actions.HTTPRoutesUpdateResult, errors.Error) {
u, err := GetUser(ctx)
if err != nil {
return nil, errors.Internal(err)
}

if err := p.Validate(); err != nil {
return nil, errors.Validation(err)
}

payload, err := act.db.PayloadsGetByUserAndName(u.ID, p.Payload)
if err == sql.ErrNoRows {
return nil, errors.NotFoundf("payload with name %q not found", p.Payload)
}

rec, err := act.db.HTTPRoutesGetByPayloadIDAndIndex(payload.ID, p.Index)
if err == sql.ErrNoRows {
return nil, errors.NotFoundf("http route for payload %q with index %d not found",
p.Payload, p.Index)
} else if err != nil {
return nil, errors.Internal(err)
}

if p.Method != nil {
rec.Method = *p.Method
}

if p.Path != nil {
rec.Path = *p.Path
}

if p.Code != nil {
rec.Code = *p.Code
}

if p.Headers != nil {
rec.Headers = p.Headers
}

if p.Body != nil {
body, err := base64.StdEncoding.DecodeString(*p.Body)

if err != nil {
return nil, errors.Validationf("body: invalid base64 data")
}

rec.Body = body
}

if p.IsDynamic != nil {
rec.IsDynamic = *p.IsDynamic
}

if err := act.db.HTTPRoutesUpdate(rec); err != nil {
return nil, errors.Internal(err)
}

return &actions.HTTPRoutesUpdateResult{HTTPRoute: HTTPRoute(*rec, payload.Subdomain)}, nil
}

func (act *dbactions) HTTPRoutesDelete(ctx context.Context, p actions.HTTPRoutesDeleteParams) (*actions.HTTPRoutesDeleteResult, errors.Error) {
u, err := GetUser(ctx)
if err != nil {
Expand Down
Loading

0 comments on commit 12c7fc8

Please sign in to comment.