package photosapi import ( "context" "crypto/ed25519" "errors" "math/rand" "net/http" "os" "os/signal" "path/filepath" "time" "github.com/gin-gonic/gin" "gorm.io/gorm" ) var ( ErrReqNotFound = errors.New("this route doesn't exists") ErrStorePathNotADirectory = errors.New("store path is not a directory") ) type Service struct { Gin *gin.Engine DB *gorm.DB Config *ServiceConfig StorageTmp *Storage StorageUpload *Storage LogOk *Logger LogErr *Logger SessionKey ed25519.PrivateKey SessionKeyValidation ed25519.PublicKey } type ServiceConfig struct { Listen string DB *DBConfig StorePath string } func GetOrGenerateKey(storePath string) ed25519.PrivateKey { p := filepath.Join(storePath, "photo.key") key, err := os.ReadFile(p) if errors.Is(err, os.ErrNotExist) { _, key, err = ed25519.GenerateKey(nil) if err != nil { panic(err) } err = os.WriteFile(p, key, 0600) if err != nil { panic(err) } } return key } func New(config *ServiceConfig) *Service { key := GetOrGenerateKey(config.StorePath) pubKey := key.Public().(ed25519.PublicKey) return &Service{ Gin: gin.New(), Config: config, StorageTmp: NewStorage(config.StorePath, "tmp"), StorageUpload: NewStorage(config.StorePath, "upload"), LogOk: &Logger{os.Stdout, "Photos"}, LogErr: &Logger{os.Stderr, "Photos"}, SessionKey: key, SessionKeyValidation: pubKey, } } func (s *Service) SetupRoutes() { s.Gin.Use( gin.Logger(), s.Recovery, s.DefaultJSON, s.HandleError, ) s.AccountInit() s.MeInit() s.FileInit() s.UploadInit() s.Gin.NoRoute(func(c *gin.Context) { c.AbortWithError(http.StatusNotFound, ErrReqNotFound) }) } func (s *Service) PrepareStore() { d, err := os.Stat(s.Config.StorePath) if err != nil { s.LogErr.Fatal("Store", err) } if !d.IsDir() { s.LogErr.Fatal("Store", ErrStorePathNotADirectory) } } func (s *Service) Run() error { rand.Seed(time.Now().UnixNano()) s.PrepareStore() s.SetupRoutes() s.SetupDB() 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 }