stufflog graphql server
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

256 lines
6.4 KiB

package main
import (
"context"
"log"
"os"
"os/signal"
"sort"
"syscall"
"time"
"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/slerrors"
"git.aiterp.net/stufflog/server/internal/space"
"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"
)
var dbDriver string
var dbConnect string
var listenAddress string
var s3Host string
var s3AccessKey string
var s3SecretKey string
var s3BucketName string
var s3MaxFileSize int64
var s3Secure bool
var s3RootDirectory string
var s3UrlRoot string
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"},
Destination: &dbDriver,
},
&cli.StringFlag{
Name: "db-connect",
Value: "stufflog_user:stuff1234@(localhost:3306)/stufflog",
Usage: "Database connection string or path",
EnvVars: []string{"DATABASE_CONNECT"},
Destination: &dbConnect,
},
&cli.StringFlag{
Name: "listenAddress",
Value: ":8000",
Usage: "Address to bind the server to.",
EnvVars: []string{"SERVER_LISTEN"},
Destination: &listenAddress,
},
&cli.StringFlag{
Name: "s3-host",
Value: "",
Usage: "S3 Host (without https://)",
EnvVars: []string{"S3_HOST"},
Destination: &s3Host,
},
&cli.StringFlag{
Name: "s3-access-key",
Value: "",
Usage: "S3 access key (not the secret key)",
EnvVars: []string{"S3_ACCESS_KEY"},
Destination: &s3AccessKey,
},
&cli.StringFlag{
Name: "s3-secret-key",
Value: "",
Usage: "S3 secret key",
EnvVars: []string{"S3_SECRET_KEY"},
Destination: &s3SecretKey,
},
&cli.StringFlag{
Name: "s3-bucket-name",
Value: "stufflog",
Usage: "S3 bucket name",
EnvVars: []string{"S3_BUCKET"},
Destination: &s3BucketName,
},
&cli.Int64Flag{
Name: "s3-max-file-size",
Value: 1024 * 1024,
Usage: "S3 maximum file size",
EnvVars: []string{"S3_MAX_FILE_SIZE"},
Destination: &s3MaxFileSize,
},
&cli.BoolFlag{
Name: "s3-secure",
Usage: "Enable HTTPS for the s3 server",
EnvVars: []string{"S3_HTTPS", "S3_SECURE"},
Value: false,
Destination: &s3Secure,
},
&cli.StringFlag{
Name: "s3-root-directory",
Value: "stufflog/userdata",
Usage: "Root directory under S3 server to use.",
EnvVars: []string{"S3_ROOT_DIRECTORY"},
Destination: &s3RootDirectory,
},
&cli.StringFlag{
Name: "s3-url-root",
Value: "",
Usage: "The URL root for public URLs; includes the root directory. It's generated if blank.",
EnvVars: []string{"S3_URL_ROOT"},
Destination: &s3UrlRoot,
},
},
Commands: []*cli.Command{
{
Name: "reset-admin",
Usage: "Reset the admin user (or create it)",
Action: func(c *cli.Context) error {
db, err := database.Open(dbDriver, dbConnect)
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 slerrors.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(dbDriver, dbConnect)
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(dbDriver, dbConnect)
if err != nil {
return errors.Wrap(err, "Failed to connect to database")
}
var s3 *space.Space
if s3Host != "" {
s3, err = space.Connect(
s3Host, s3AccessKey, s3SecretKey, s3BucketName,
s3Secure, s3MaxFileSize, s3RootDirectory, s3UrlRoot,
)
if err != nil {
return err
}
}
bundle := services.NewBundle(db, s3)
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(listenAddress)
if err != nil {
errCh <- err
}
}()
select {
case sig := <-exitSignal:
{
log.Println("Received signal", sig)
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)
}
}