package command import ( "cmp" "context" "fmt" "net/url" "slices" "strings" "time" "github.com/StevanFreeborn/gator/internal/database" "github.com/StevanFreeborn/gator/internal/rss" "github.com/StevanFreeborn/gator/internal/state" "github.com/google/uuid" ) type Command struct { Name string Handler CommandHandler } type CommandHandler func(s *state.State) error func newCommand(name string, handler CommandHandler) *Command { return &Command{ Name: name, Handler: handler, } } type CommandRegistry struct { commands map[string]*Command } func (c *CommandRegistry) register(cmd *Command) error { c.commands[cmd.Name] = cmd return nil } func NewRegistry() *CommandRegistry { cr := CommandRegistry{ commands: map[string]*Command{}, } commands := []*Command{ loginCommand(), registerCommand(), resetCommand(), usersCommand(), aggCommand(), addFeedCommand(), feedsCommand(), followCommand(), followingCommand(), unfollowCommand(), } for _, cmd := range commands { cr.register(cmd) } return &cr } func (c *CommandRegistry) RunCommand(cmdName string, s *state.State) error { cmd, found := c.commands[cmdName] if !found { return fmt.Errorf("No '%s' registered", cmdName) } return cmd.Handler(s) } func loginCommand() *Command { return newCommand("login", func(s *state.State) error { if len(s.Arguments) == 0 { return fmt.Errorf("Did not receive expected username argument") } username := s.Arguments[0] _, err := s.Database.GetUserByName(context.Background(), username) if err != nil { return fmt.Errorf("Failed to login") } err = s.Config.SetUser(username) if err != nil { return err } fmt.Printf("Current user set to '%s'\n", username) return nil }) } func registerCommand() *Command { return newCommand("register", func(s *state.State) error { if len(s.Arguments) == 0 { return fmt.Errorf("Did not receive expected username argument") } createUserParams := database.CreateUserParams{ ID: uuid.New(), CreatedAt: time.Now(), UpdatedAt: time.Now(), Name: s.Arguments[0], } createdUser, err := s.Database.CreateUser(context.Background(), createUserParams) if err != nil { return err } s.Config.SetUser(createdUser.Name) fmt.Printf("Successfully registered user '%s':\n", createdUser.Name) fmt.Printf(" Id => %s\n", createdUser.ID) fmt.Printf(" CreatedAt => %s\n", createdUser.CreatedAt) fmt.Printf(" UpdatedAt => %s\n", createdUser.UpdatedAt) fmt.Printf("Current user set to user '%s':\n", createdUser.Name) return nil }) } func resetCommand() *Command { return newCommand("reset", func(s *state.State) error { err := s.Database.DeleteAllUsers(context.Background()) if err != nil { return err } fmt.Println("Successfully delete all users") return nil }) } func usersCommand() *Command { return newCommand("users", func(s *state.State) error { users, err := s.Database.GetAllUsers(context.Background()) if err != nil { return err } slices.SortFunc(users, func(a, b database.User) int { return cmp.Compare(a.Name, b.Name) }) for _, user := range users { msg := "* %s" if user.Name == s.Config.CurrentUserName { msg += " (current)" } fmt.Printf(msg+"\n", user.Name) } return nil }) } func aggCommand() *Command { return newCommand("agg", func(s *state.State) error { // if len(s.Arguments) == 0 { // return fmt.Errorf("Did not receive expected feed argument") // } // feed := s.Arguments[0] // // validUrl, err := url.Parse(feed) // // if err != nil { // return fmt.Errorf("Feed '%s' is not a valid url", feed) // } rssFeed, err := rss.FetchFeed(context.Background(), "https://www.wagslane.dev/index.xml") if err != nil { return err } fmt.Printf("%v\n", rssFeed) return nil }) } func addFeedCommand() *Command { return newCommand("addfeed", requiresLoggedInUser(func(s *state.State, currentUser database.User) error { if len(s.Arguments) < 2 { return fmt.Errorf("Did not receive expected feed name and url") } feedName := s.Arguments[0] feedUrl := s.Arguments[1] validatedFeedUrl, err := url.Parse(feedUrl) if strings.TrimSpace(feedName) == "" { return fmt.Errorf("Feed name cannot be empty") } if err != nil { return fmt.Errorf("Feed url '%s' is not a valid url", feedUrl) } createFeedParams := database.CreateFeedParams{ ID: uuid.New(), UserID: currentUser.ID, Name: feedName, Url: validatedFeedUrl.String(), CreatedAt: time.Now(), UpdatedAt: time.Now(), } createdFeed, err := s.Database.CreateFeed(context.Background(), createFeedParams) if err != nil { return err } createFollowParams := database.CreateFollowParams{ ID: uuid.New(), CreatedAt: time.Now(), UpdatedAt: time.Now(), UserID: currentUser.ID, FeedID: createdFeed.ID, } _, err = s.Database.CreateFollow(context.Background(), createFollowParams) if err != nil { return err } fmt.Printf("Successfully added and followed feed '%s' with url '%s'\n", createdFeed.Name, createdFeed.Url) fmt.Printf(" Id => %s\n", createdFeed.ID) fmt.Printf(" UserId => %s\n", createdFeed.UserID) fmt.Printf(" CreatedAt => %s\n", createdFeed.CreatedAt) fmt.Printf(" UpdatedAt => %s\n", createdFeed.UpdatedAt) return nil })) } func feedsCommand() *Command { return newCommand("feeds", func(s *state.State) error { feeds, err := s.Database.GetAllFeeds(context.Background()) if err != nil { return err } for _, feed := range feeds { feedsUserName := "Unknown" user, err := s.Database.GetUserById(context.Background(), feed.UserID) if err == nil { feedsUserName = user.Name } fmt.Printf("* %s [%s] (%s)\n", feed.Name, feed.Url, feedsUserName) } return nil }) } func followCommand() *Command { return newCommand("follow", requiresLoggedInUser(func(s *state.State, currentUser database.User) error { if len(s.Arguments) == 0 { return fmt.Errorf("Did not receive expected url argument") } urlForFeedToFollow := s.Arguments[0] feed, err := s.Database.GetFeedByUrl(context.Background(), urlForFeedToFollow) if err != nil { return fmt.Errorf("Unable to find feed with url '%s'", urlForFeedToFollow) } createFollowParams := database.CreateFollowParams{ ID: uuid.New(), CreatedAt: time.Now(), UpdatedAt: time.Now(), FeedID: feed.ID, UserID: currentUser.ID, } createdFollow, err := s.Database.CreateFollow(context.Background(), createFollowParams) if err != nil { return fmt.Errorf("Unable to follow feed '%s'", feed.Name) } fmt.Printf("'%s' successfully followed feed '%s'", createdFollow.UserName, createdFollow.FeedName) return nil })) } func followingCommand() *Command { return newCommand("following", requiresLoggedInUser(func(s *state.State, currentUser database.User) error { follows, err := s.Database.GetFollowsForUser(context.Background(), currentUser.ID) if err != nil { return fmt.Errorf("Unable to find follows for user") } for _, follow := range follows { fmt.Printf("* %s", follow.FeedName) } return nil })) } func unfollowCommand() *Command { return newCommand("unfollow", requiresLoggedInUser(func(s *state.State, currentUser database.User) error { if len(s.Arguments) == 0 { return fmt.Errorf("Did not receive expected feed url argument") } feedUrl := s.Arguments[0] feed, err := s.Database.GetFeedByUrl(context.Background(), feedUrl) if err != nil { return fmt.Errorf("Unable to find feed with url '%s'", feedUrl) } deleteFollowParams := database.DeleteFollowForUserParams{ UserID: currentUser.ID, FeedID: feed.ID, } err = s.Database.DeleteFollowForUser(context.Background(), deleteFollowParams) if err != nil { return fmt.Errorf("Unable to unfollow feed '%s'", feed.Name) } fmt.Printf("Successfully unfollowed feed '%s'", feed.Name) return nil })) }