1package cmd
  2
  3import (
  4	"strconv"
  5	"strings"
  6	"time"
  7
  8	"github.com/caarlos0/duration"
  9	"github.com/caarlos0/tablewriter"
 10	"github.com/charmbracelet/soft-serve/pkg/backend"
 11	"github.com/charmbracelet/soft-serve/pkg/proto"
 12	"github.com/dustin/go-humanize"
 13	"github.com/spf13/cobra"
 14)
 15
 16// TokenCommand returns a command that manages user access tokens.
 17func TokenCommand() *cobra.Command {
 18	cmd := &cobra.Command{
 19		Use:     "token",
 20		Aliases: []string{"access-token"},
 21		Short:   "Manage access tokens",
 22	}
 23
 24	var createExpiresIn string
 25	createCmd := &cobra.Command{
 26		Use:   "create NAME",
 27		Short: "Create a new access token",
 28		Args:  cobra.MinimumNArgs(1),
 29		RunE: func(cmd *cobra.Command, args []string) error {
 30			ctx := cmd.Context()
 31			be := backend.FromContext(ctx)
 32			name := strings.Join(args, " ")
 33
 34			user := proto.UserFromContext(ctx)
 35			if user == nil {
 36				return proto.ErrUserNotFound
 37			}
 38
 39			var expiresAt time.Time
 40			var expiresIn time.Duration
 41			if createExpiresIn != "" {
 42				d, err := duration.Parse(createExpiresIn)
 43				if err != nil {
 44					return err
 45				}
 46
 47				expiresIn = d
 48				expiresAt = time.Now().Add(d)
 49			}
 50
 51			token, err := be.CreateAccessToken(ctx, user, name, expiresAt)
 52			if err != nil {
 53				return err
 54			}
 55
 56			notice := "Access token created"
 57			if expiresIn != 0 {
 58				notice += " (expires in " + humanize.Time(expiresAt) + ")"
 59			}
 60
 61			cmd.PrintErrln(notice)
 62			cmd.Println(token)
 63
 64			return nil
 65		},
 66	}
 67
 68	createCmd.Flags().StringVar(&createExpiresIn, "expires-in", "", "Token expiration time (e.g. 1y, 3mo, 2w, 5d4h, 1h30m)")
 69
 70	listCmd := &cobra.Command{
 71		Use:     "list",
 72		Aliases: []string{"ls"},
 73		Short:   "List access tokens",
 74		Args:    cobra.NoArgs,
 75		RunE: func(cmd *cobra.Command, _ []string) error {
 76			ctx := cmd.Context()
 77			be := backend.FromContext(ctx)
 78
 79			user := proto.UserFromContext(ctx)
 80			if user == nil {
 81				return proto.ErrUserNotFound
 82			}
 83
 84			tokens, err := be.ListAccessTokens(ctx, user)
 85			if err != nil {
 86				return err
 87			}
 88
 89			if len(tokens) == 0 {
 90				cmd.Println("No tokens found")
 91				return nil
 92			}
 93
 94			now := time.Now()
 95			return tablewriter.Render(
 96				cmd.OutOrStdout(),
 97				tokens,
 98				[]string{"ID", "Name", "Created At", "Expires In"},
 99				func(t proto.AccessToken) ([]string, error) {
100					expiresAt := "-"
101					if !t.ExpiresAt.IsZero() {
102						if now.After(t.ExpiresAt) {
103							expiresAt = "expired"
104						} else {
105							expiresAt = humanize.Time(t.ExpiresAt)
106						}
107					}
108
109					return []string{
110						strconv.FormatInt(t.ID, 10),
111						t.Name,
112						humanize.Time(t.CreatedAt),
113						expiresAt,
114					}, nil
115				},
116			)
117		},
118	}
119
120	deleteCmd := &cobra.Command{
121		Use:     "delete ID",
122		Aliases: []string{"rm", "remove"},
123		Short:   "Delete an access token",
124		Args:    cobra.ExactArgs(1),
125		RunE: func(cmd *cobra.Command, args []string) error {
126			ctx := cmd.Context()
127			be := backend.FromContext(ctx)
128
129			user := proto.UserFromContext(ctx)
130			if user == nil {
131				return proto.ErrUserNotFound
132			}
133
134			id, err := strconv.ParseInt(args[0], 10, 64)
135			if err != nil {
136				return err
137			}
138
139			if err := be.DeleteAccessToken(ctx, user, id); err != nil {
140				return err
141			}
142
143			cmd.PrintErrln("Access token deleted")
144			return nil
145		},
146	}
147
148	cmd.AddCommand(
149		createCmd,
150		listCmd,
151		deleteCmd,
152	)
153
154	return cmd
155}