package main import ( "context" "encoding/json" "fmt" "io" "log" "net/http" "os" "os/exec" "path/filepath" "recipe-manager/config" "recipe-manager/data" "recipe-manager/routers" "recipe-manager/services/oauth" "strings" "github.com/go-chi/chi/v5" "github.com/go-chi/cors" "github.com/spf13/viper" "golang.org/x/oauth2" ) func loadConfig(path string) (*config.ServerConfig, error) { viper.AddConfigPath(path) viper.SetConfigName("app") viper.SetConfigType("env") viper.AutomaticEnv() var config config.ServerConfig err := viper.ReadInConfig() if err != nil { return nil, err } err = viper.Unmarshal(&config) if err != nil { return nil, err } return &config, nil } type Server struct { server *http.Server data *data.Data cfg *config.ServerConfig oauth oauth.OAuthService } func NewServer() *Server { serverCfg, err := loadConfig(".") if err != nil { log.Fatal(err) } return &Server{ server: &http.Server{Addr: fmt.Sprintf(":%d", serverCfg.ServerPort)}, data: data.NewData(), cfg: serverCfg, oauth: oauth.NewOAuthService(serverCfg), } } func (s *Server) Run() error { s.createHandler() log.Printf("Server running on %s", s.server.Addr) 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"}, })) database := data.NewData() // Auth Router r.Group(func(r chi.Router) { ar := routers.NewAuthRouter(s.cfg, s.oauth) ar.Route(r) }) // Protect Group r.Group(func(r chi.Router) { r.Use(func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { token := &oauth2.Token{} if cookie, err := r.Cookie("access_token"); err == nil { token.AccessToken = cookie.Value } user, err := s.oauth.GetUserInfo(r.Context(), token) if err != nil { // if have refresh token, set refresh token to token if cookie, err := r.Cookie("refresh_token"); err == nil { token.RefreshToken = cookie.Value } newToken, err := s.oauth.RefreshToken(r.Context(), token) if err != nil { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } // set new token to cookie w.Header().Add("set-cookie", fmt.Sprintf("access_token=%s; Path=/; HttpOnly; SameSite=None; Secure; Max-Age=3600", newToken.AccessToken)) } ctx := context.WithValue(r.Context(), "user", user) next.ServeHTTP(w, r.WithContext(ctx)) }) }) r.Post("/merge", func(w http.ResponseWriter, r *http.Request) { var targetMap map[string]interface{} err := json.NewDecoder(r.Body).Decode(&targetMap) if err != nil { w.WriteHeader(http.StatusBadRequest) log.Fatalln("Merge request failed: ", err) return } repo_path := "cofffeemachineConfig/coffeethai02_" master_version := targetMap["master"].(string) dev_version := targetMap["dev"].(string) output_path := targetMap["output"].(string) changelog_path := targetMap["changelog"].(string) // find target file in the cofffeemachineConfig if _, err := os.Stat(repo_path + master_version + ".json"); err != nil { w.WriteHeader(http.StatusBadRequest) log.Fatalln("Merge request failed. Master file not found: ", err) return } if _, err := os.Stat(repo_path + dev_version + ".json"); err != nil { w.WriteHeader(http.StatusBadRequest) log.Fatalln("Merge request failed. Dev file not found: ", err) return } master_path := repo_path + master_version + ".json" dev_path := repo_path + dev_version + ".json" // Get who's requesting user := r.Context().Value("user").(map[string]interface{}) log.Println("User: ", user["email"].(string)) // lookup for python exec py_exec, err := exec.LookPath("python") if err != nil { log.Fatalln("Python error: ", err) } else { py_exec, err = filepath.Abs(py_exec) } log.Println("Found python exec: ", py_exec) // target api file merge_api, api_err := os.Open("./python_api/merge_recipe.py") if api_err != nil { log.Fatalln("Merge request failed. Python api error: ", api_err) } defer merge_api.Close() log.Println("Locate python api", merge_api.Name()) cmd := exec.Command(py_exec, merge_api.Name(), "merge", master_path, dev_path, output_path, changelog_path, "", user["email"].(string)) log.Println("Run merge command", cmd) out, err := cmd.CombinedOutput() log.Println(string(out)) if err != nil { log.Fatalln("Merge request failed. Python merge failed: ", err) } w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{"message": "Merge success"}) }) r.Post("/dllog", func(w http.ResponseWriter, r *http.Request) { var postRequest map[string]interface{} err := json.NewDecoder(r.Body).Decode(&postRequest) if err != nil { w.WriteHeader(http.StatusBadRequest) log.Fatalln("Log request failed: ", err) return } file_ext := ".html" if rb, ok := postRequest["htmlfile"].(bool); ok { if rj, ok := postRequest["requestJson"].(bool); ok { if rj { file_ext = ".json" } else if !rb && !rj { file_ext = ".log" } } } log.Println("Log file ext: ", file_ext) changelog_path := "cofffeemachineConfig/changelog/testlog" + file_ext logFile, err := os.Open(changelog_path) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } defer logFile.Close() if file_ext == ".json" { var logFileJson map[string]interface{} err = json.NewDecoder(logFile).Decode(&logFileJson) if err != nil { log.Fatalf("Error when decode log file: %s", err) } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(logFileJson) log.Println("Log file: ", changelog_path) } else { w.Header().Set("Content-Disposition", "attachment; filename=logfile"+file_ext) w.Header().Set("Content-Type", "application/octet-stream") _, err = io.Copy(w, logFile) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } }) // Recipe Router rr := routers.NewRecipeRouter(database) rr.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)}) }) s.server.Handler = r } func (s *Server) Shutdown(ctx context.Context) error { return s.server.Shutdown(ctx) }