feat: implemented initial login command
This commit is contained in:
+19
@@ -0,0 +1,19 @@
|
|||||||
|
# Copyright (c) 2026 Stevan Freeborn
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# gator
|
||||||
|
|
||||||
|
This is a blog aggregator that was built as part of a [boot.dev](https://boot.dev) course.
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/StevanFreeborn/gator/internal/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Config.SetUser(username)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Current user set to '%s'\n", username)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
const GATOR_CONFIG_FILE = ".gatorconfig.json"
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
DbUrl string `json:"db_url"`
|
||||||
|
CurrentUserName string `json:"current_user_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Read() (*Config, error) {
|
||||||
|
config := Config{}
|
||||||
|
|
||||||
|
configFilePath, err := getConfigPath()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(configFilePath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &config)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: I don't think there should be
|
||||||
|
// hidden I/O in this method call
|
||||||
|
func (c *Config) SetUser(currentUserName string) error {
|
||||||
|
c.CurrentUserName = currentUserName
|
||||||
|
return writeFile(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) String() string {
|
||||||
|
return fmt.Sprintf("Config { DbUrl = %s, CurrentUserName = %s }", c.DbUrl, c.CurrentUserName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFile(config *Config) error {
|
||||||
|
data, err := json.Marshal(config)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
configFilePath, err := getConfigPath()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create(configFilePath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
_, err = file.Write(data)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConfigPath() (string, error) {
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
configFilePath := path.Join(homeDir, GATOR_CONFIG_FILE)
|
||||||
|
|
||||||
|
return configFilePath, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package state
|
||||||
|
|
||||||
|
import "github.com/StevanFreeborn/gator/internal/config"
|
||||||
|
|
||||||
|
type State struct {
|
||||||
|
Config *config.Config
|
||||||
|
Arguments []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewState(c *config.Config, args []string) *State {
|
||||||
|
return &State{Config: c, Arguments: args}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/StevanFreeborn/gator/internal/command"
|
||||||
|
"github.com/StevanFreeborn/gator/internal/config"
|
||||||
|
"github.com/StevanFreeborn/gator/internal/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
fmt.Println("Not enough arguments provided. Usage: <command> <args>")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := config.Read()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error reading config file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := os.Args[1]
|
||||||
|
args := os.Args[2:]
|
||||||
|
|
||||||
|
s := state.NewState(c, args)
|
||||||
|
|
||||||
|
registry := command.NewRegistry()
|
||||||
|
err = registry.RunCommand(cmd, s)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error running '%s' command: %s\n", cmd, err)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- TODO: Finish writing SQL migration
|
||||||
|
-- for users table
|
||||||
|
-- +goose Up
|
||||||
|
CREATE TABLE users (
|
||||||
|
id UUID
|
||||||
|
);
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
DROP TABLE users;
|
||||||
Reference in New Issue
Block a user