Source code of DERO Merchant
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.

251 lines
7.2KB

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "log"
  7. "net/http"
  8. "os"
  9. "os/signal"
  10. "syscall"
  11. "time"
  12. "github.com/gin-contrib/gzip"
  13. "github.com/gin-gonic/gin"
  14. _ "github.com/lib/pq"
  15. "github.com/peppinux/dero-merchant/api"
  16. "github.com/peppinux/dero-merchant/auth"
  17. "github.com/peppinux/dero-merchant/coingecko"
  18. "github.com/peppinux/dero-merchant/config"
  19. "github.com/peppinux/dero-merchant/postgres"
  20. "github.com/peppinux/dero-merchant/processor"
  21. "github.com/peppinux/dero-merchant/redis"
  22. "github.com/peppinux/dero-merchant/webapp"
  23. "github.com/peppinux/dero-merchant/webapp/store"
  24. "github.com/peppinux/dero-merchant/webapp/user"
  25. )
  26. func main() {
  27. // Logger setup (logs to both shell and file)
  28. os.Mkdir("./logs/", 0775)
  29. logFilePath := fmt.Sprintf("./logs/%v.log", time.Now())
  30. logFile, err := os.Create(logFilePath)
  31. if err != nil {
  32. log.Fatalln("Error creating log file:", err)
  33. }
  34. gin.DefaultWriter = io.MultiWriter(logFile, os.Stdout)
  35. log.SetOutput(gin.DefaultWriter)
  36. log.Println("Logger set up.")
  37. // Load configuration
  38. err = config.LoadFromENV("./.env")
  39. if err != nil {
  40. log.Fatalln("Config: error loading .env file:", err)
  41. }
  42. log.Println("Config: loaded from .env file.")
  43. // PostgreSQL init
  44. postgres.DB, err = postgres.Connect(config.DBName, config.DBUser, config.DBPassword, config.DBHost, config.DBPort, "disable") // TODO: Enable SSLMode?
  45. if err != nil {
  46. log.Fatalln("Error connecting to PostgresSQL database:", err)
  47. }
  48. defer postgres.DB.Close()
  49. err = postgres.DB.Ping()
  50. if err != nil {
  51. log.Fatalln("PostgreSQL Server: OFFLINE.")
  52. } else {
  53. log.Println("PostgreSQL Server: ONLINE.")
  54. }
  55. postgres.CreateTablesIfNotExist()
  56. // Redis init
  57. redis.Pool = redis.NewPool(config.RedisAddress)
  58. defer redis.Pool.Close()
  59. err = redis.Ping()
  60. if err != nil {
  61. log.Fatalln("Redis Server: OFFLINE.")
  62. } else {
  63. log.Println("Redis Server: ONLINE.")
  64. }
  65. redis.FlushAll()
  66. // CoinGecko API V3 server status check
  67. statusCode := coingecko.Ping()
  68. if statusCode == http.StatusOK {
  69. log.Println("CoinGecko API V3: ONLINE.")
  70. } else {
  71. log.Fatalln("CoinGecko API V3: OFFLINE.")
  72. }
  73. // Payment processor init
  74. processor.ActiveWallets = processor.NewStoresWallets()
  75. err = processor.SetupDaemonConnection()
  76. if err != nil {
  77. log.Fatalf("Error setting up connection to daemon %s: %v\n", config.DeroDaemonAddress, err)
  78. }
  79. log.Printf("DERO Network: Connected to %s daemon %s\n", config.DeroNetwork, config.DeroDaemonAddress)
  80. err = processor.CreateWalletsDirectory()
  81. if err != nil {
  82. log.Fatalln("Error creating wallets directory:", err)
  83. }
  84. log.Println("Wallets directory created.")
  85. // In case of application restart after a crash, update the status of pending payments to "error"
  86. // since the application could have not been able to check for them.
  87. err = processor.CleanAllPendingPayments()
  88. if err != nil {
  89. log.Println("Error cleaning all pending payments:", err)
  90. }
  91. // Router init
  92. r := gin.Default()
  93. r.Use(gzip.Gzip(gzip.DefaultCompression))
  94. r.Static("/static", "./webassets/static")
  95. r.StaticFile("/license", "./LICENSE")
  96. r.StaticFile("/docs", "./documentation/docs.html")
  97. r.LoadHTMLGlob("./webassets/templates/**/*")
  98. r.NoRoute(webapp.Error404Handler)
  99. // Web App routes
  100. web := r.Group("", auth.SessionAuth())
  101. {
  102. web.GET("/", webapp.IndexHandler)
  103. userGroup := web.Group("/user")
  104. {
  105. notAuthOrRedirect := userGroup.Group("", auth.SessionNotAuthOrRedirect())
  106. {
  107. notAuthOrRedirect.GET("/signup", user.SignUpGetHandler)
  108. notAuthOrRedirect.POST("/signup", user.SignUpPostHandler)
  109. notAuthOrRedirect.GET("/signin", user.SignInGetHandler)
  110. notAuthOrRedirect.POST("/signin", user.SignInPostHandler)
  111. notAuthOrRedirect.GET("/forgot_password", user.ForgotPasswordGetHandler)
  112. notAuthOrRedirect.POST("/forgot_password", user.ForgotPasswordPostHandler)
  113. notAuthOrRedirect.GET("/recover", user.RecoverGetHandler)
  114. notAuthOrRedirect.POST("/recover", user.RecoverPostHandler)
  115. }
  116. authOrRedirect := userGroup.Group("", auth.SessionAuthOrRedirect())
  117. {
  118. authOrRedirect.POST("/signout", user.SignOutHandler)
  119. authOrRedirect.POST("/signout_all", user.SignOutAllHandler)
  120. }
  121. userGroup.GET("/verify", user.VerifyHandler)
  122. userGroup.GET("/new_verification_token", user.NewVerificationTokenGetHandler)
  123. userGroup.POST("/new_verification_token", user.NewVerificationTokenPostHandler)
  124. authOrForbidden := userGroup.Group("", auth.SessionAuthOrForbidden())
  125. {
  126. authOrForbidden.PUT("", user.PutHandler)
  127. }
  128. }
  129. dashboard := web.Group("/dashboard", auth.SessionAuthOrRedirect())
  130. {
  131. dashboard.GET("", webapp.DashboardHandler)
  132. dashboard.GET("/account", webapp.MyAccountHandler)
  133. stores := dashboard.Group("/stores")
  134. {
  135. stores.GET("", webapp.MyStoresHandler)
  136. stores.GET("/view/:id", webapp.ViewStoreHandler)
  137. stores.GET("/add", webapp.AddStoreGetHandler)
  138. stores.POST("/add", webapp.AddStorePostHandler)
  139. stores.GET("/view/:id/payments", webapp.ViewStorePaymentsHandler)
  140. }
  141. }
  142. storeGroup := web.Group("/store", auth.SessionAuthOrForbidden())
  143. {
  144. storeGroup.GET("/:id/payments", store.PaymentsGetHandler)
  145. requirePassword := storeGroup.Group("", auth.RequireUserPassword())
  146. {
  147. requirePassword.PUT("/:id", store.PutHandler)
  148. requirePassword.DELETE("/:id", store.DeleteHandler)
  149. }
  150. }
  151. }
  152. // Pay helper endpoint for customers
  153. r.GET("/pay/:payment_id", webapp.PayHandler)
  154. // Web Socket handler used by /pay/:payment_id to update payment's status on page
  155. r.GET("/ws/payment/:payment_id/status", webapp.WSPaymentStatusHandler)
  156. // API routes
  157. apiGroup := r.Group("/api")
  158. {
  159. v1 := apiGroup.Group("/v1", auth.APIKeyAuth())
  160. {
  161. v1.GET("/ping", api.PingGetHandler)
  162. payment := v1.Group("/payment")
  163. {
  164. requireSecretKey := payment.Group("", auth.SecretKeyAuth())
  165. {
  166. requireSecretKey.POST("", api.PaymentPostHandler)
  167. }
  168. payment.GET("/:payment_id", api.PaymentGetHandler)
  169. }
  170. v1.POST("/payments", api.PaymentsPostHandler)
  171. v1.GET("/payments", api.PaymentsGetHandler)
  172. }
  173. }
  174. // Secure web server configuration suggested on CloudFlare Blog https://blog.cloudflare.com/exposing-go-on-the-internet/
  175. /*tlsConfig := &tls.Config{
  176. PreferServerCipherSuites: true,
  177. CurvePreferences: []tls.CurveID{
  178. tls.CurveP256,
  179. tls.X25519,
  180. },
  181. }*/
  182. srv := &http.Server{
  183. Addr: fmt.Sprintf(":%d", config.ServerPort),
  184. ReadTimeout: 5 * time.Second,
  185. WriteTimeout: 10 * time.Second,
  186. IdleTimeout: 120 * time.Second,
  187. //TLSConfig: tlsConfig,
  188. Handler: r,
  189. }
  190. go func() {
  191. // TODO: Add TLS, edit ServerPort in 443. Redirect http requests to https using unrolled/secure? Also Edit ws in wss pay.js
  192. if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
  193. log.Fatalln("Error running server:", err)
  194. }
  195. }()
  196. // Graceful shutdown
  197. quit := make(chan os.Signal, 1)
  198. signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
  199. <-quit
  200. log.Println("Cleaning all pending payments...")
  201. err = processor.CleanAllPendingPayments()
  202. if err != nil {
  203. log.Println("Error cleaning all pending payments:", err)
  204. }
  205. log.Println("Gracefully shutting down server...")
  206. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  207. defer cancel()
  208. if err := srv.Shutdown(ctx); err != nil {
  209. log.Fatalln("Error shutting down server:", err)
  210. }
  211. log.Println("Server shut down.")
  212. }