package main import ( "context" "encoding/json" "fmt" "log" "net/http" "recipe-manager/config" "recipe-manager/data" "recipe-manager/enums/permissions" "recipe-manager/helpers" "recipe-manager/middlewares" "recipe-manager/models" "recipe-manager/routers" routersV2 "recipe-manager/routers/v2" "recipe-manager/services/logger" "recipe-manager/services/oauth" "recipe-manager/services/recipe" "recipe-manager/services/sheet" "recipe-manager/services/user" "strings" "github.com/jmoiron/sqlx" "github.com/redis/go-redis/v9" "github.com/go-chi/chi/v5" "github.com/go-chi/cors" "go.uber.org/zap" ) const VERSION = "1.0.35" type Server struct { server *http.Server data *data.Data database *sqlx.DB cache_db *data.RedisCli cfg *config.ServerConfig oauth oauth.OAuthService taoLogger *logger.TaoLogger } var update_noti *redis.PubSub func NewServer(cfg *config.ServerConfig, oauthService oauth.OAuthService) *Server { taoLogger := logger.NewTaoLogger(cfg) taoLogger.Log = taoLogger.Log.Named("Server") redisClient := data.NewRedisClient("redis:6379", "") update_noti = redisClient.Subscribe("updater.noti") _, err := update_noti.Receive(context.Background()) if err != nil { taoLogger.Log.Error("Failed to subscribe to updater.noti", zap.Error(err)) } return &Server{ server: &http.Server{Addr: fmt.Sprintf(":%d", cfg.ServerPort)}, data: data.NewData(taoLogger, redisClient), database: data.NewSqliteDatabase(), cache_db: redisClient, cfg: cfg, oauth: oauth.NewOAuthService(cfg), taoLogger: taoLogger, } } func (s *Server) Run() error { //go cli.CommandLineListener() s.createHandler() // log.Printf("Server running on %s", s.server.Addr) s.taoLogger.Log.Info("Server running", zap.String("addr", s.server.Addr)) s.taoLogger.Log.Info("Version", zap.String("version", VERSION)) go s.UpgradeListener(context.Background()) defer func(Log *zap.Logger) { err := Log.Sync() if err != nil { log.Fatal(err) } }(s.taoLogger.Log) return s.server.ListenAndServe() } func (s *Server) createHandler() { r := chi.NewRouter() r.Use(cors.Handler(cors.Options{ AllowedOrigins: strings.Split(s.cfg.AllowedOrigins, ","), AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"}, AllowCredentials: true, ExposedHeaders: []string{"Content-Type"}, AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"}, })) // Recipe Service recipeService := recipe.NewRecipeService(s.data, s.taoLogger) // User Service userService := user.NewUserService(s.cfg, s.database, s.taoLogger) // Generate SuperAdmin permissions for seed perms := helpers.LoadCountrySettingsWithPermissions() superPerm := perms[0].CountryPermission for _, p := range perms { superPerm = superPerm | p.CountryPermission } if superPerm < 3999 { superPerm = superPerm | permissions.Editor | permissions.Viewer } // Seed _ = userService.CreateNewUser(context.WithValue(context.Background(), "user", &models.User{Email: "system"}), "kenta420", "poomipat.c@forth.co.th", "", permissions.Permission(superPerm)) _ = userService.CreateNewUser(context.WithValue(context.Background(), "user", &models.User{Email: "system"}), "phu", "pakin.t@forth.co.th", "", permissions.Permission(superPerm)) _ = userService.CreateNewUser(context.WithValue(context.Background(), "user", &models.User{Email: "system"}), "wanlop", "wanlop.r@forth.co.th", "", permissions.Permission(superPerm)) _ = userService.CreateNewUser(context.WithValue(context.Background(), "user", &models.User{Email: "system"}), "dawit", "dawit.o@forth.co.th", "", permissions.Permission(superPerm)) _ = userService.CreateNewUser(context.WithValue(context.Background(), "user", &models.User{Email: "system"}), "narisara", "narisara.k@tao-bin.com", "", permissions.Permission(superPerm)) // Auth Router r.Group(func(r chi.Router) { ar := routers.NewAuthRouter(s.cfg, s.oauth, userService, s.taoLogger) ar.Route(r) }) // Initial redis for k, v := range s.data.CurrentRecipe { s.taoLogger.Log.Debug("Caching", zap.Any("Recipe", k)) s.cache_db.SetToKey(k, v) } // Protected Group r.Group(func(r chi.Router) { r.Use(func(next http.Handler) http.Handler { return middlewares.Authorize(s.oauth, userService, next) }) sheetService, err := sheet.NewSheetService(context.Background(), s.cfg) if err != nil { s.taoLogger.Log.Fatal("Error while trying to create sheet service: ", zap.Error(err)) return } // Recipe Router rr := routers.NewRecipeRouter(s.data, recipeService, sheetService, s.taoLogger, s.cache_db, userService) rr.Route(r) // Material Router mr := routers.NewMaterialRouter(s.data, s.taoLogger) mr.Route(r) // Topping Router tr := routers.NewToppingRouter(s.data, s.taoLogger) tr.Route(r) // User Router ur := routers.NewUserRouter(s.taoLogger, userService) ur.Route(r) // //fmt.Println("routers", r.Routes()) }) // Protected Group V2 r.Group(func(r chi.Router) { r.Route("/v2", func(r chi.Router) { r.Use(func(next http.Handler) http.Handler { return middlewares.Authorize(s.oauth, userService, next) }) // Recipe Router rr := routersV2.NewRecipeRouter(s.data, s.taoLogger) rr.Route(r) }) }) nscr := routers.NewServiceCheckRouter(s.cache_db) nscr.Route(r) // routers.NewToppingRouter(s.data, s.taoLogger).Route(r) r.NotFound(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusNotFound) json.NewEncoder(w).Encode(map[string]string{"message": fmt.Sprintf("path %s are not exits", r.RequestURI)}) }) // server status // upgrade r.Route("/upgrade", func(r chi.Router) { r.Get("/", func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{"message": "upgrade"}) // fmt.Fprintf(w, "upgrade") // add delay for 10 secs // TODO: send to upgrade listener // s.server.Shutdown(context.Background()) }) }) // display all routes [DEBUG] // chi.Walk(r, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error { // //fmt.Println(method, " ---> ", route) // return nil // }) s.server.Handler = r } func (s *Server) UpgradeListener(ctx context.Context) { if update_noti != nil { s.taoLogger.Log.Debug("Subscribed to updater.noti") ch := update_noti.Channel() for msg := range ch { switch msg.Channel { case "updater.noti": s.taoLogger.Log.Info("Received upgrade notification") // s.server.Shutdown(ctx) payload := msg.Payload s.taoLogger.Log.Debug("Received payload", zap.Any("payload", payload)) if payload == "new_version" { s.taoLogger.Log.Info("New version available, shutting down server") s.server.Shutdown(ctx) } } } } } func (s *Server) Shutdown(ctx context.Context) error { return s.server.Shutdown(ctx) }