package main import ( "context" "git.aiterp.net/stufflog/server/database" "git.aiterp.net/stufflog/server/graph" "git.aiterp.net/stufflog/server/internal/generate" "git.aiterp.net/stufflog/server/internal/xlerrors" "git.aiterp.net/stufflog/server/models" "git.aiterp.net/stufflog/server/services" "github.com/99designs/gqlgen/graphql/playground" "github.com/gin-gonic/gin" "github.com/pkg/errors" "github.com/pressly/goose" "github.com/urfave/cli/v2" "log" "os" "os/signal" "sort" "syscall" "time" ) func main() { app := &cli.App{ Name: "stufflog", Usage: "Issue tracker for your home and hobbies", Flags: []cli.Flag{ &cli.StringFlag{ Name: "db-driver", Value: "mysql", Usage: "Database driver", EnvVars: []string{"DATABASE_DRIVER"}, }, &cli.StringFlag{ Name: "db-connect", Value: "stufflog_user:stuff1234@(localhost:3306)/stufflog", Usage: "Database connection string or path", EnvVars: []string{"DATABASE_CONNECT"}, }, &cli.StringFlag{ Name: "listen", Value: ":8000", Usage: "Address to bind the server to.", EnvVars: []string{"SERVER_LISTEN"}, }, }, Commands: []*cli.Command{ { Name: "reset-admin", Usage: "Reset the admin user (or create it)", Action: func(c *cli.Context) error { db, err := database.Open(c.String("db-driver"), c.String("db-connect")) if err != nil { return errors.Wrap(err, "Failed to connect to database") } timeout, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() newPass := generate.Generate(20, "") admin, err := db.Users().Find(timeout, "Admin") if xlerrors.IsNotFound(err) { admin = &models.User{ ID: "Admin", Name: "Administrator", Active: true, Admin: true, } err = admin.SetPassword(newPass) if err != nil { return errors.Wrap(err, "Failed to set password") } _, err = db.Users().Insert(timeout, *admin) if err != nil { return errors.Wrap(err, "Failed to inset user") } } else if err != nil { return err } else { err = admin.SetPassword(newPass) if err != nil { return errors.Wrap(err, "Failed to set password") } err = db.Users().Save(timeout, *admin) if err != nil { return errors.Wrap(err, "Failed to save user") } } log.Println("Username:", admin.ID) log.Println("Password:", newPass) return nil }, }, { Name: "migrate", Usage: "Migrate the configured database", Action: func(c *cli.Context) error { db, err := database.Open(c.String("db-driver"), c.String("db-connect")) if err != nil { return errors.Wrap(err, "Failed to connect to database") } err = db.Migrate() if err == goose.ErrNoNextVersion { log.Println("No more migrations to run") } else if err != nil { return errors.Wrap(err, "Failed to run migration") } log.Println("Migration succeeded") return nil }, }, { Name: "server", Usage: "Run the server", Action: func(c *cli.Context) error { db, err := database.Open(c.String("db-driver"), c.String("db-connect")) if err != nil { return errors.Wrap(err, "Failed to connect to database") } bundle := services.NewBundle(db) server := gin.New() server.GET("/graphql", graph.Gin(bundle, db)) server.POST("/graphql", graph.Gin(bundle, db)) server.GET("/playground", gin.WrapH(playground.Handler("StuffLog GraphQL Playground", "/graphql"))) exitSignal := make(chan os.Signal) signal.Notify(exitSignal, os.Interrupt, os.Kill, syscall.SIGTERM) errCh := make(chan error) go func() { err := server.Run(c.String("listen")) if err != nil { errCh <- err } }() select { case signal := <-exitSignal: { log.Println("Received signal", signal) return nil } case err := <-errCh: { return err } } }, }, }, } sort.Sort(cli.FlagsByName(app.Flags)) sort.Sort(cli.CommandsByName(app.Commands)) err := app.Run(os.Args) if err != nil { log.Fatal(err) } }