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.

253 lines
6.4 KiB

  1. package main
  2. import (
  3. "context"
  4. "log"
  5. "os"
  6. "os/signal"
  7. "sort"
  8. "syscall"
  9. "time"
  10. "git.aiterp.net/stufflog/server/database"
  11. "git.aiterp.net/stufflog/server/graph"
  12. "git.aiterp.net/stufflog/server/internal/generate"
  13. "git.aiterp.net/stufflog/server/internal/slerrors"
  14. "git.aiterp.net/stufflog/server/internal/space"
  15. "git.aiterp.net/stufflog/server/models"
  16. "git.aiterp.net/stufflog/server/services"
  17. "github.com/99designs/gqlgen/graphql/playground"
  18. "github.com/gin-gonic/gin"
  19. "github.com/pkg/errors"
  20. "github.com/pressly/goose"
  21. "github.com/urfave/cli/v2"
  22. )
  23. var dbDriver string
  24. var dbConnect string
  25. var listenAddress string
  26. var s3Host string
  27. var s3AccessKey string
  28. var s3SecretKey string
  29. var s3BucketName string
  30. var s3MaxFileSize int64
  31. var s3Secure bool
  32. var s3RootDirectory string
  33. var s3UrlRoot string
  34. func main() {
  35. app := &cli.App{
  36. Name: "stufflog",
  37. Usage: "Issue tracker for your home and hobbies",
  38. Flags: []cli.Flag{
  39. &cli.StringFlag{
  40. Name: "db-driver",
  41. Value: "mysql",
  42. Usage: "Database driver",
  43. EnvVars: []string{"DATABASE_DRIVER"},
  44. Destination: &dbDriver,
  45. },
  46. &cli.StringFlag{
  47. Name: "db-connect",
  48. Value: "stufflog_user:stuff1234@(localhost:3306)/stufflog",
  49. Usage: "Database connection string or path",
  50. EnvVars: []string{"DATABASE_CONNECT"},
  51. Destination: &dbConnect,
  52. },
  53. &cli.StringFlag{
  54. Name: "listenAddress",
  55. Value: ":8000",
  56. Usage: "Address to bind the server to.",
  57. EnvVars: []string{"SERVER_LISTEN"},
  58. Destination: &listenAddress,
  59. },
  60. &cli.StringFlag{
  61. Name: "s3-host",
  62. Value: "localhost",
  63. Usage: "S3 Host (without https://)",
  64. EnvVars: []string{"S3_HOST"},
  65. Destination: &s3Host,
  66. },
  67. &cli.StringFlag{
  68. Name: "s3-access-key",
  69. Value: "",
  70. Usage: "S3 access key (not the secret key)",
  71. EnvVars: []string{"S3_ACCESS_KEY"},
  72. Destination: &s3AccessKey,
  73. },
  74. &cli.StringFlag{
  75. Name: "s3-secret-key",
  76. Value: "",
  77. Usage: "S3 secret key",
  78. EnvVars: []string{"S3_SECRET_KEY"},
  79. Destination: &s3SecretKey,
  80. },
  81. &cli.StringFlag{
  82. Name: "s3-bucket-name",
  83. Value: "stufflog",
  84. Usage: "S3 bucket name",
  85. EnvVars: []string{"S3_BUCKET"},
  86. Destination: &s3BucketName,
  87. },
  88. &cli.Int64Flag{
  89. Name: "s3-max-file-size",
  90. Value: 1024 * 1024,
  91. Usage: "S3 maximum file size",
  92. EnvVars: []string{"S3_MAX_FILE_SIZE"},
  93. Destination: &s3MaxFileSize,
  94. },
  95. &cli.BoolFlag{
  96. Name: "s3-secure",
  97. Usage: "Enable HTTPS for the s3 server",
  98. EnvVars: []string{"S3_HTTPS", "S3_SECURE"},
  99. Value: false,
  100. Destination: &s3Secure,
  101. },
  102. &cli.StringFlag{
  103. Name: "s3-root-directory",
  104. Value: "stufflog/userdata",
  105. Usage: "Root directory under S3 server to use.",
  106. EnvVars: []string{"S3_ROOT_DIRECTORY"},
  107. Destination: &s3RootDirectory,
  108. },
  109. &cli.StringFlag{
  110. Name: "s3-url-root",
  111. Value: "",
  112. Usage: "The URL root for public URLs; includes the root directory. It's generated if blank.",
  113. EnvVars: []string{"S3_URL_ROOT"},
  114. Destination: &s3UrlRoot,
  115. },
  116. },
  117. Commands: []*cli.Command{
  118. {
  119. Name: "reset-admin",
  120. Usage: "Reset the admin user (or create it)",
  121. Action: func(c *cli.Context) error {
  122. db, err := database.Open(dbDriver, dbConnect)
  123. if err != nil {
  124. return errors.Wrap(err, "Failed to connect to database")
  125. }
  126. timeout, cancel := context.WithTimeout(context.Background(), time.Second*5)
  127. defer cancel()
  128. newPass := generate.Generate(20, "")
  129. admin, err := db.Users().Find(timeout, "Admin")
  130. if slerrors.IsNotFound(err) {
  131. admin = &models.User{
  132. ID: "Admin",
  133. Name: "Administrator",
  134. Active: true,
  135. Admin: true,
  136. }
  137. err = admin.SetPassword(newPass)
  138. if err != nil {
  139. return errors.Wrap(err, "Failed to set password")
  140. }
  141. _, err = db.Users().Insert(timeout, *admin)
  142. if err != nil {
  143. return errors.Wrap(err, "Failed to inset user")
  144. }
  145. } else if err != nil {
  146. return err
  147. } else {
  148. err = admin.SetPassword(newPass)
  149. if err != nil {
  150. return errors.Wrap(err, "Failed to set password")
  151. }
  152. err = db.Users().Save(timeout, *admin)
  153. if err != nil {
  154. return errors.Wrap(err, "Failed to save user")
  155. }
  156. }
  157. log.Println("Username:", admin.ID)
  158. log.Println("Password:", newPass)
  159. return nil
  160. },
  161. },
  162. {
  163. Name: "migrate",
  164. Usage: "Migrate the configured database",
  165. Action: func(c *cli.Context) error {
  166. db, err := database.Open(dbDriver, dbConnect)
  167. if err != nil {
  168. return errors.Wrap(err, "Failed to connect to database")
  169. }
  170. err = db.Migrate()
  171. if err == goose.ErrNoNextVersion {
  172. log.Println("No more migrations to run")
  173. } else if err != nil {
  174. return errors.Wrap(err, "Failed to run migration")
  175. }
  176. log.Println("Migration succeeded")
  177. return nil
  178. },
  179. },
  180. {
  181. Name: "server",
  182. Usage: "Run the server",
  183. Action: func(c *cli.Context) error {
  184. db, err := database.Open(dbDriver, dbConnect)
  185. if err != nil {
  186. return errors.Wrap(err, "Failed to connect to database")
  187. }
  188. s3, err := space.Connect(
  189. s3Host, s3AccessKey, s3SecretKey, s3BucketName,
  190. s3Secure, s3MaxFileSize, s3RootDirectory, s3UrlRoot,
  191. )
  192. if err != nil {
  193. return err
  194. }
  195. bundle := services.NewBundle(db, s3)
  196. server := gin.New()
  197. server.GET("/graphql", graph.Gin(bundle, db))
  198. server.POST("/graphql", graph.Gin(bundle, db))
  199. server.GET("/playground", gin.WrapH(playground.Handler("StuffLog GraphQL Playground", "/graphql")))
  200. exitSignal := make(chan os.Signal)
  201. signal.Notify(exitSignal, os.Interrupt, os.Kill, syscall.SIGTERM)
  202. errCh := make(chan error)
  203. go func() {
  204. err := server.Run(listenAddress)
  205. if err != nil {
  206. errCh <- err
  207. }
  208. }()
  209. select {
  210. case sig := <-exitSignal:
  211. {
  212. log.Println("Received signal", sig)
  213. return nil
  214. }
  215. case err := <-errCh:
  216. {
  217. return err
  218. }
  219. }
  220. },
  221. },
  222. },
  223. }
  224. sort.Sort(cli.FlagsByName(app.Flags))
  225. sort.Sort(cli.CommandsByName(app.Commands))
  226. err := app.Run(os.Args)
  227. if err != nil {
  228. log.Fatal(err)
  229. }
  230. }