package api import ( "context" "math/rand" "net/http" "os" "os/signal" "time" "github.com/gin-gonic/gin" "github.com/go-sql-driver/mysql" "gitlab.celogeek.com/photos/api/internal/photoserrors" "gitlab.celogeek.com/photos/api/internal/store" "gorm.io/gorm" ) type Service struct { Gin *gin.Engine DB *gorm.DB Config *ServiceConfig Store *store.Store LogOk *Logger LogErr *Logger } type ServiceConfig struct { Listen string DB *mysql.Config StorePath string } func New(config *ServiceConfig) *Service { return &Service{ Gin: gin.New(), Config: config, Store: &store.Store{Path: config.StorePath}, LogOk: &Logger{os.Stdout, "Photos"}, LogErr: &Logger{os.Stderr, "Photos"}, } } func (s *Service) SetupRoutes() { s.Gin.Use(gin.Logger()) s.Gin.Use(s.Recovery) s.Gin.Use(s.RequireBody) ac := s.Gin.Group("/account") ac.POST("/signup", s.Signup) ac.POST("/login", s.Login) ac.GET("/logout", s.RequireAuthToken, s.Logout) s.Gin.GET("/me", s.RequireSession, func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "success", "user": s.CurrentSession(c).Account.Login, }) }) file := s.Gin.Group("/file") file.Use(s.RequireSession) file.POST("", s.FileCreate) file.HEAD("/:checksum", s.FileExists) file.GET("/:checksum", s.FileGet) file.POST("/chunk", s.FileCreateChunk) file.HEAD("/chunk/:checksum", s.FileChunkExists) file.GET("/analyze/:checksum", s.FileAnalyze) s.Gin.NoRoute(func(c *gin.Context) { s.Error(c, http.StatusNotFound, photoserrors.ErrReqNotFound) }) } func (s *Service) PrepareStore() { d, err := os.Stat(s.Store.Path) if err != nil { s.LogErr.Fatal("Store", err) } if !d.IsDir() { s.LogErr.Fatal("Store", photoserrors.ErrStorePathNotADirectory) } } func (s *Service) Run() error { rand.Seed(time.Now().UnixNano()) s.PrepareStore() s.SetupRoutes() s.SetupDB() go s.SessionCleaner() srv := &http.Server{ Addr: s.Config.Listen, Handler: s.Gin, } quit := make(chan os.Signal, 1) var runError error go func() { // service connections if runError = srv.ListenAndServe(); runError != nil { quit <- os.Interrupt } }() // Wait for interrupt signal to gracefully shutdown the server with // a timeout of 5 seconds. signal.Notify(quit, os.Interrupt) <-quit s.LogOk.Print("Exit", "shutdown ...") ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { return err } db, err := s.DB.DB() if err != nil { return err } s.LogOk.Print("Exit", "closing database ...") if err := db.Close(); err != nil { return err } s.LogOk.Print("Exit", "exiting") return runError }