From 2012dfe01bb76c9aa72c1da23361963e37776c85 Mon Sep 17 00:00:00 2001 From: Dane Schneider Date: Fri, 27 Sep 2024 09:32:11 -0700 Subject: [PATCH] some updates for more flexibile routing and server shutdown for cloud --- .dockerignore | 5 + app/.dockerignore | 2 + app/server/handlers/auth_helpers.go | 38 ++++++- app/server/handlers/sessions.go | 1 + app/server/main.go | 3 + app/server/routes/routes.go | 156 ++++++++++++++++------------ app/server/setup/setup.go | 66 ++++++++---- app/server/types/trial.go | 4 +- 8 files changed, 181 insertions(+), 94 deletions(-) create mode 100644 .dockerignore create mode 100644 app/.dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..7e79f30e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +dist-server/ +cli/ +node_modules/ +plandex-server +plandex-cloud diff --git a/app/.dockerignore b/app/.dockerignore new file mode 100644 index 00000000..e7608d5c --- /dev/null +++ b/app/.dockerignore @@ -0,0 +1,2 @@ +cli/ +plandex-server \ No newline at end of file diff --git a/app/server/handlers/auth_helpers.go b/app/server/handlers/auth_helpers.go index 32f52474..238d6350 100644 --- a/app/server/handlers/auth_helpers.go +++ b/app/server/handlers/auth_helpers.go @@ -11,6 +11,7 @@ import ( "plandex-server/hooks" "plandex-server/types" "strings" + "time" "github.com/plandex/plandex/shared" ) @@ -90,6 +91,11 @@ func ClearAuthCookieIfBrowser(w http.ResponseWriter, r *http.Request) error { return fmt.Errorf("error retrieving auth cookie: %v", err) } + var domain string + if os.Getenv("GOENV") == "production" { + domain = os.Getenv("APP_SUBDOMAIN") + ".plandex.ai" + } + // Clear the authToken cookie http.SetCookie(w, &http.Cookie{ Name: "authToken", @@ -99,8 +105,11 @@ func ClearAuthCookieIfBrowser(w http.ResponseWriter, r *http.Request) error { Secure: os.Getenv("GOENV") != "development", HttpOnly: true, SameSite: http.SameSiteLaxMode, + Domain: domain, }) + log.Println("cleared auth cookie") + return nil } @@ -129,6 +138,10 @@ func ClearAccountFromCookies(w http.ResponseWriter, r *http.Request, userId stri encodedAccounts := base64.URLEncoding.EncodeToString(updatedAccountsBytes) // Set the updated accounts cookie + var domain string + if os.Getenv("GOENV") == "production" { + domain = os.Getenv("APP_SUBDOMAIN") + ".plandex.ai" + } http.SetCookie(w, &http.Cookie{ Name: "accounts", Path: "/", @@ -136,6 +149,7 @@ func ClearAccountFromCookies(w http.ResponseWriter, r *http.Request, userId stri Secure: os.Getenv("GOENV") != "development", HttpOnly: true, SameSite: http.SameSiteLaxMode, + Domain: domain, }) return nil @@ -180,14 +194,25 @@ func SetAuthCookieIfBrowser(w http.ResponseWriter, r *http.Request, user *db.Use // base64 encode token = base64.URLEncoding.EncodeToString(bytes) - http.SetCookie(w, &http.Cookie{ + var domain string + if os.Getenv("GOENV") == "production" { + domain = os.Getenv("APP_SUBDOMAIN") + ".plandex.ai" + } + + cookie := &http.Cookie{ Name: "authToken", Path: "/", Value: "Bearer " + token, Secure: os.Getenv("GOENV") != "development", HttpOnly: true, SameSite: http.SameSiteLaxMode, - }) + Domain: domain, + Expires: time.Now().Add(time.Hour * 24 * 90), + } + + log.Println("setting auth cookie", cookie) + + http.SetCookie(w, cookie) storedAccounts, err := GetAccountsFromCookie(r) @@ -232,6 +257,8 @@ func SetAuthCookieIfBrowser(w http.ResponseWriter, r *http.Request, user *db.Use Secure: os.Getenv("GOENV") != "development", HttpOnly: true, SameSite: http.SameSiteLaxMode, + Domain: domain, + Expires: time.Now().Add(time.Hour * 24 * 90), }) return nil @@ -309,6 +336,8 @@ func ValidateAndSignIn(w http.ResponseWriter, r *http.Request, req shared.SignIn log.Printf("Error validating email verification: %v\n", err) return nil, fmt.Errorf("error validating email verification: %v", err) } + + log.Println("Email verification successful") } // start a transaction @@ -347,12 +376,14 @@ func ValidateAndSignIn(w http.ResponseWriter, r *http.Request, req shared.SignIn } } else { // update email verification with user and auth token ids - _, err = tx.Exec("UPDATE email verifications SET user_id = $1, auth_token_id = $2 WHERE id = $3", user.Id, authTokenId, emailVerificationId) + _, err = tx.Exec("UPDATE email_verifications SET user_id = $1, auth_token_id = $2 WHERE id = $3", user.Id, authTokenId, emailVerificationId) if err != nil { log.Printf("Error updating email verification: %v\n", err) return nil, fmt.Errorf("error updating email verification: %v", err) } + + log.Println("Email verification updated") } // commit transaction @@ -387,6 +418,7 @@ func ValidateAndSignIn(w http.ResponseWriter, r *http.Request, req shared.SignIn orgId = orgs[0].Id } + log.Println("Setting auth cookie if browser") err = SetAuthCookieIfBrowser(w, r, user, token, orgId) if err != nil { log.Printf("Error setting auth cookie: %v\n", err) diff --git a/app/server/handlers/sessions.go b/app/server/handlers/sessions.go index 0429de1e..98faa48e 100644 --- a/app/server/handlers/sessions.go +++ b/app/server/handlers/sessions.go @@ -217,6 +217,7 @@ func SignInHandler(w http.ResponseWriter, r *http.Request) { return } + log.Println("Validating and signing in") resp, err := ValidateAndSignIn(w, r, req) if err != nil { diff --git a/app/server/main.go b/app/server/main.go index c632c393..83ab0e88 100644 --- a/app/server/main.go +++ b/app/server/main.go @@ -2,6 +2,7 @@ package main import ( "log" + "plandex-server/routes" "plandex-server/setup" "github.com/gorilla/mux" @@ -12,6 +13,8 @@ func main() { log.SetFlags(log.LstdFlags | log.Lmicroseconds) r := mux.NewRouter() + routes.AddHealthRoutes(r) + routes.AddApiRoutes(r) setup.MustLoadIp() setup.MustInitDb() diff --git a/app/server/routes/routes.go b/app/server/routes/routes.go index 8001233e..293ce0c2 100644 --- a/app/server/routes/routes.go +++ b/app/server/routes/routes.go @@ -5,29 +5,37 @@ import ( "log" "net/http" "os" + "path/filepath" "plandex-server/handlers" "plandex-server/hooks" "github.com/gorilla/mux" ) -func AddApiRoutes(r *mux.Router) { +func AddHealthRoutes(r *mux.Router) { r.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { - _, apiErr := hooks.ExecHook(hooks.HealthCheck, hooks.HookParams{}) - if apiErr != nil { log.Printf("Error in health check hook: %v\n", apiErr) http.Error(w, apiErr.Msg, apiErr.Status) return } - fmt.Fprint(w, "OK") }) r.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) { + // Log the host + host := r.Host + log.Printf("Host header: %s", host) + + execPath, err := os.Executable() + if err != nil { + log.Fatal("Error getting current directory: ", err) + } + currentDir := filepath.Dir(execPath) + // get version from version.txt - bytes, err := os.ReadFile("version.txt") + bytes, err := os.ReadFile(filepath.Join(currentDir, "..", "version.txt")) if err != nil { http.Error(w, "Error getting version", http.StatusInternalServerError) @@ -36,91 +44,101 @@ func AddApiRoutes(r *mux.Router) { fmt.Fprint(w, string(bytes)) }) +} + +func AddApiRoutes(r *mux.Router) { + addApiRoutes(r, "") +} + +func AddApiRoutesWithPrefix(r *mux.Router, prefix string) { + addApiRoutes(r, prefix) +} - r.HandleFunc("/accounts/email_verifications", handlers.CreateEmailVerificationHandler).Methods("POST") - r.HandleFunc("/accounts/email_verifications/check_pin", handlers.CheckEmailPinHandler).Methods("POST") - r.HandleFunc("/accounts/sign_in_codes", handlers.CreateSignInCodeHandler).Methods("POST") - r.HandleFunc("/accounts/sign_in", handlers.SignInHandler).Methods("POST") - r.HandleFunc("/accounts/sign_out", handlers.SignOutHandler).Methods("POST") - r.HandleFunc("/accounts", handlers.CreateAccountHandler).Methods("POST") +func addApiRoutes(r *mux.Router, prefix string) { + r.HandleFunc(prefix+"/accounts/email_verifications", handlers.CreateEmailVerificationHandler).Methods("POST") + r.HandleFunc(prefix+"/accounts/email_verifications/check_pin", handlers.CheckEmailPinHandler).Methods("POST") + r.HandleFunc(prefix+"/accounts/sign_in_codes", handlers.CreateSignInCodeHandler).Methods("POST") + r.HandleFunc(prefix+"/accounts/sign_in", handlers.SignInHandler).Methods("POST") + r.HandleFunc(prefix+"/accounts/sign_out", handlers.SignOutHandler).Methods("POST") + r.HandleFunc(prefix+"/accounts", handlers.CreateAccountHandler).Methods("POST") - r.HandleFunc("/orgs/session", handlers.GetOrgSessionHandler).Methods("GET") - r.HandleFunc("/orgs", handlers.ListOrgsHandler).Methods("GET") - r.HandleFunc("/orgs", handlers.CreateOrgHandler).Methods("POST") + r.HandleFunc(prefix+"/orgs/session", handlers.GetOrgSessionHandler).Methods("GET") + r.HandleFunc(prefix+"/orgs", handlers.ListOrgsHandler).Methods("GET") + r.HandleFunc(prefix+"/orgs", handlers.CreateOrgHandler).Methods("POST") - r.HandleFunc("/users", handlers.ListUsersHandler).Methods("GET") - r.HandleFunc("/orgs/users/{userId}", handlers.DeleteOrgUserHandler).Methods("DELETE") - r.HandleFunc("/orgs/roles", handlers.ListOrgRolesHandler).Methods("GET") + r.HandleFunc(prefix+"/users", handlers.ListUsersHandler).Methods("GET") + r.HandleFunc(prefix+"/orgs/users/{userId}", handlers.DeleteOrgUserHandler).Methods("DELETE") + r.HandleFunc(prefix+"/orgs/roles", handlers.ListOrgRolesHandler).Methods("GET") - r.HandleFunc("/invites", handlers.InviteUserHandler).Methods("POST") - r.HandleFunc("/invites/pending", handlers.ListPendingInvitesHandler).Methods("GET") - r.HandleFunc("/invites/accepted", handlers.ListAcceptedInvitesHandler).Methods("GET") - r.HandleFunc("/invites/all", handlers.ListAllInvitesHandler).Methods("GET") - r.HandleFunc("/invites/{inviteId}", handlers.DeleteInviteHandler).Methods("DELETE") + r.HandleFunc(prefix+"/invites", handlers.InviteUserHandler).Methods("POST") + r.HandleFunc(prefix+"/invites/pending", handlers.ListPendingInvitesHandler).Methods("GET") + r.HandleFunc(prefix+"/invites/accepted", handlers.ListAcceptedInvitesHandler).Methods("GET") + r.HandleFunc(prefix+"/invites/all", handlers.ListAllInvitesHandler).Methods("GET") + r.HandleFunc(prefix+"/invites/{inviteId}", handlers.DeleteInviteHandler).Methods("DELETE") - r.HandleFunc("/projects", handlers.CreateProjectHandler).Methods("POST") - r.HandleFunc("/projects", handlers.ListProjectsHandler).Methods("GET") - r.HandleFunc("/projects/{projectId}/set_plan", handlers.ProjectSetPlanHandler).Methods("PUT") - r.HandleFunc("/projects/{projectId}/rename", handlers.RenameProjectHandler).Methods("PUT") + r.HandleFunc(prefix+"/projects", handlers.CreateProjectHandler).Methods("POST") + r.HandleFunc(prefix+"/projects", handlers.ListProjectsHandler).Methods("GET") + r.HandleFunc(prefix+"/projects/{projectId}/set_plan", handlers.ProjectSetPlanHandler).Methods("PUT") + r.HandleFunc(prefix+"/projects/{projectId}/rename", handlers.RenameProjectHandler).Methods("PUT") - r.HandleFunc("/projects/{projectId}/plans/current_branches", handlers.GetCurrentBranchByPlanIdHandler).Methods("POST") + r.HandleFunc(prefix+"/projects/{projectId}/plans/current_branches", handlers.GetCurrentBranchByPlanIdHandler).Methods("POST") - r.HandleFunc("/plans", handlers.ListPlansHandler).Methods("GET") - r.HandleFunc("/plans/archive", handlers.ListArchivedPlansHandler).Methods("GET") - r.HandleFunc("/plans/ps", handlers.ListPlansRunningHandler).Methods("GET") + r.HandleFunc(prefix+"/plans", handlers.ListPlansHandler).Methods("GET") + r.HandleFunc(prefix+"/plans/archive", handlers.ListArchivedPlansHandler).Methods("GET") + r.HandleFunc(prefix+"/plans/ps", handlers.ListPlansRunningHandler).Methods("GET") - r.HandleFunc("/projects/{projectId}/plans", handlers.CreatePlanHandler).Methods("POST") + r.HandleFunc(prefix+"/projects/{projectId}/plans", handlers.CreatePlanHandler).Methods("POST") - r.HandleFunc("/projects/{projectId}/plans", handlers.CreatePlanHandler).Methods("DELETE") + r.HandleFunc(prefix+"/projects/{projectId}/plans", handlers.CreatePlanHandler).Methods("DELETE") - r.HandleFunc("/plans/{planId}", handlers.GetPlanHandler).Methods("GET") - r.HandleFunc("/plans/{planId}", handlers.DeletePlanHandler).Methods("DELETE") + r.HandleFunc(prefix+"/plans/{planId}", handlers.GetPlanHandler).Methods("GET") + r.HandleFunc(prefix+"/plans/{planId}", handlers.DeletePlanHandler).Methods("DELETE") - r.HandleFunc("/plans/{planId}/{branch}/tell", handlers.TellPlanHandler).Methods("POST") + r.HandleFunc(prefix+"/plans/{planId}/{branch}/tell", handlers.TellPlanHandler).Methods("POST") - r.HandleFunc("/plans/{planId}/{branch}/respond_missing_file", handlers.RespondMissingFileHandler).Methods("POST") + r.HandleFunc(prefix+"/plans/{planId}/{branch}/respond_missing_file", handlers.RespondMissingFileHandler).Methods("POST") - r.HandleFunc("/plans/{planId}/{branch}/build", handlers.BuildPlanHandler).Methods("PATCH") - r.HandleFunc("/plans/{planId}/{branch}/connect", handlers.ConnectPlanHandler).Methods("PATCH") - r.HandleFunc("/plans/{planId}/{branch}/stop", handlers.StopPlanHandler).Methods("DELETE") + r.HandleFunc(prefix+"/plans/{planId}/{branch}/build", handlers.BuildPlanHandler).Methods("PATCH") + r.HandleFunc(prefix+"/plans/{planId}/{branch}/connect", handlers.ConnectPlanHandler).Methods("PATCH") + r.HandleFunc(prefix+"/plans/{planId}/{branch}/stop", handlers.StopPlanHandler).Methods("DELETE") - r.HandleFunc("/plans/{planId}/{branch}/current_plan", handlers.CurrentPlanHandler).Methods("GET") - r.HandleFunc("/plans/{planId}/{branch}/apply", handlers.ApplyPlanHandler).Methods("PATCH") - r.HandleFunc("/plans/{planId}/archive", handlers.ArchivePlanHandler).Methods("PATCH") - r.HandleFunc("/plans/{planId}/unarchive", handlers.UnarchivePlanHandler).Methods("PATCH") + r.HandleFunc(prefix+"/plans/{planId}/{branch}/current_plan", handlers.CurrentPlanHandler).Methods("GET") + r.HandleFunc(prefix+"/plans/{planId}/{branch}/apply", handlers.ApplyPlanHandler).Methods("PATCH") + r.HandleFunc(prefix+"/plans/{planId}/archive", handlers.ArchivePlanHandler).Methods("PATCH") + r.HandleFunc(prefix+"/plans/{planId}/unarchive", handlers.UnarchivePlanHandler).Methods("PATCH") - r.HandleFunc("/plans/{planId}/rename", handlers.RenamePlanHandler).Methods("PATCH") - r.HandleFunc("/plans/{planId}/{branch}/reject_all", handlers.RejectAllChangesHandler).Methods("PATCH") - r.HandleFunc("/plans/{planId}/{branch}/reject_file", handlers.RejectFileHandler).Methods("PATCH") - r.HandleFunc("/plans/{planId}/{branch}/reject_files", handlers.RejectFilesHandler).Methods("PATCH") - r.HandleFunc("/plans/{planId}/{branch}/diffs", handlers.GetPlanDiffsHandler).Methods("GET") + r.HandleFunc(prefix+"/plans/{planId}/rename", handlers.RenamePlanHandler).Methods("PATCH") + r.HandleFunc(prefix+"/plans/{planId}/{branch}/reject_all", handlers.RejectAllChangesHandler).Methods("PATCH") + r.HandleFunc(prefix+"/plans/{planId}/{branch}/reject_file", handlers.RejectFileHandler).Methods("PATCH") + r.HandleFunc(prefix+"/plans/{planId}/{branch}/reject_files", handlers.RejectFilesHandler).Methods("PATCH") + r.HandleFunc(prefix+"/plans/{planId}/{branch}/diffs", handlers.GetPlanDiffsHandler).Methods("GET") - r.HandleFunc("/plans/{planId}/{branch}/context", handlers.ListContextHandler).Methods("GET") - r.HandleFunc("/plans/{planId}/{branch}/context", handlers.LoadContextHandler).Methods("POST") - r.HandleFunc("/plans/{planId}/{branch}/context", handlers.UpdateContextHandler).Methods("PUT") - r.HandleFunc("/plans/{planId}/{branch}/context", handlers.DeleteContextHandler).Methods("DELETE") + r.HandleFunc(prefix+"/plans/{planId}/{branch}/context", handlers.ListContextHandler).Methods("GET") + r.HandleFunc(prefix+"/plans/{planId}/{branch}/context", handlers.LoadContextHandler).Methods("POST") + r.HandleFunc(prefix+"/plans/{planId}/{branch}/context", handlers.UpdateContextHandler).Methods("PUT") + r.HandleFunc(prefix+"/plans/{planId}/{branch}/context", handlers.DeleteContextHandler).Methods("DELETE") - r.HandleFunc("/plans/{planId}/{branch}/convo", handlers.ListConvoHandler).Methods("GET") - r.HandleFunc("/plans/{planId}/{branch}/rewind", handlers.RewindPlanHandler).Methods("PATCH") - r.HandleFunc("/plans/{planId}/{branch}/logs", handlers.ListLogsHandler).Methods("GET") + r.HandleFunc(prefix+"/plans/{planId}/{branch}/convo", handlers.ListConvoHandler).Methods("GET") + r.HandleFunc(prefix+"/plans/{planId}/{branch}/rewind", handlers.RewindPlanHandler).Methods("PATCH") + r.HandleFunc(prefix+"/plans/{planId}/{branch}/logs", handlers.ListLogsHandler).Methods("GET") - r.HandleFunc("/plans/{planId}/branches", handlers.ListBranchesHandler).Methods("GET") - r.HandleFunc("/plans/{planId}/branches/{branch}", handlers.DeleteBranchHandler).Methods("DELETE") - r.HandleFunc("/plans/{planId}/{branch}/branches", handlers.CreateBranchHandler).Methods("POST") + r.HandleFunc(prefix+"/plans/{planId}/branches", handlers.ListBranchesHandler).Methods("GET") + r.HandleFunc(prefix+"/plans/{planId}/branches/{branch}", handlers.DeleteBranchHandler).Methods("DELETE") + r.HandleFunc(prefix+"/plans/{planId}/{branch}/branches", handlers.CreateBranchHandler).Methods("POST") - r.HandleFunc("/plans/{planId}/{branch}/settings", handlers.GetSettingsHandler).Methods("GET") - r.HandleFunc("/plans/{planId}/{branch}/settings", handlers.UpdateSettingsHandler).Methods("PUT") + r.HandleFunc(prefix+"/plans/{planId}/{branch}/settings", handlers.GetSettingsHandler).Methods("GET") + r.HandleFunc(prefix+"/plans/{planId}/{branch}/settings", handlers.UpdateSettingsHandler).Methods("PUT") - r.HandleFunc("/plans/{planId}/{branch}/status", handlers.GetPlanStatusHandler).Methods("GET") + r.HandleFunc(prefix+"/plans/{planId}/{branch}/status", handlers.GetPlanStatusHandler).Methods("GET") - r.HandleFunc("/custom_models", handlers.ListCustomModelsHandler).Methods("GET") - r.HandleFunc("/custom_models", handlers.CreateCustomModelHandler).Methods("POST") - r.HandleFunc("/custom_models/{modelId}", handlers.DeleteAvailableModelHandler).Methods("DELETE") + r.HandleFunc(prefix+"/custom_models", handlers.ListCustomModelsHandler).Methods("GET") + r.HandleFunc(prefix+"/custom_models", handlers.CreateCustomModelHandler).Methods("POST") + r.HandleFunc(prefix+"/custom_models/{modelId}", handlers.DeleteAvailableModelHandler).Methods("DELETE") - r.HandleFunc("/model_sets", handlers.ListModelPacksHandler).Methods("GET") - r.HandleFunc("/model_sets", handlers.CreateModelPackHandler).Methods("POST") - r.HandleFunc("/model_sets/{setId}", handlers.DeleteModelPackHandler).Methods("DELETE") + r.HandleFunc(prefix+"/model_sets", handlers.ListModelPacksHandler).Methods("GET") + r.HandleFunc(prefix+"/model_sets", handlers.CreateModelPackHandler).Methods("POST") + r.HandleFunc(prefix+"/model_sets/{setId}", handlers.DeleteModelPackHandler).Methods("DELETE") - r.HandleFunc("/default_settings", handlers.GetDefaultSettingsHandler).Methods("GET") - r.HandleFunc("/default_settings", handlers.UpdateDefaultSettingsHandler).Methods("PUT") + r.HandleFunc(prefix+"/default_settings", handlers.GetDefaultSettingsHandler).Methods("GET") + r.HandleFunc(prefix+"/default_settings", handlers.UpdateDefaultSettingsHandler).Methods("PUT") } diff --git a/app/server/setup/setup.go b/app/server/setup/setup.go index 5db1d6c6..8f0b2113 100644 --- a/app/server/setup/setup.go +++ b/app/server/setup/setup.go @@ -2,6 +2,7 @@ package setup import ( "context" + "fmt" "log" "net/http" "os" @@ -9,11 +10,9 @@ import ( "plandex-server/db" "plandex-server/host" "plandex-server/model/plan" - "plandex-server/routes" "syscall" "time" - "github.com/gorilla/mux" "github.com/rs/cors" ) @@ -41,7 +40,13 @@ func MustInitDb() { } } -func StartServer(r *mux.Router) { +var shutdownHooks []func() + +func RegisterShutdownHook(hook func()) { + shutdownHooks = append(shutdownHooks, hook) +} + +func StartServer(handler http.Handler) { if os.Getenv("GOENV") == "development" { log.Println("In development mode.") } @@ -62,8 +67,6 @@ func StartServer(r *mux.Router) { externalPort = "8080" } - routes.AddApiRoutes(r) - // Enable CORS based on environment var corsHandler http.Handler if os.Getenv("GOENV") == "development" { @@ -72,14 +75,14 @@ func StartServer(r *mux.Router) { AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"}, AllowedHeaders: []string{"Content-Type", "Authorization"}, AllowCredentials: true, - }).Handler(r) + }).Handler(handler) } else { corsHandler = cors.New(cors.Options{ - AllowedOrigins: []string{"http://app.plandex.ai", "http://localhost:55000"}, + AllowedOrigins: []string{fmt.Sprintf("https://%s.plandex.ai", os.Getenv("APP_SUBDOMAIN"))}, AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"}, AllowedHeaders: []string{"Content-Type", "Authorization"}, AllowCredentials: true, - }).Handler(r) + }).Handler(handler) } server := &http.Server{ @@ -93,32 +96,55 @@ func StartServer(r *mux.Router) { } }() - log.Println("Started server on port " + externalPort) + log.Println("Started Plandex server on port " + externalPort) // Capture SIGTERM and SIGINT signals sigTermChan := make(chan os.Signal, 1) signal.Notify(sigTermChan, syscall.SIGTERM, syscall.SIGINT) <-sigTermChan - log.Println("Shutting down server gracefully...") + log.Println("Plandex server shutting down gracefully...") // Context with a 5-second timeout to allow ongoing requests to finish - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + // wait for active plans to finish for up to 2 hours + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Hour) defer cancel() + // Wait for active plans to complete or timeout + log.Println("Waiting for any active plans to complete...") + select { + case <-ctx.Done(): + log.Println("Timeout waiting for active plans. Forcing shutdown.") + case <-waitForActivePlans(): + log.Println("All active plans finished.") + } + + log.Println("Shutting down http server...") + ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() if err := server.Shutdown(ctx); err != nil { - log.Printf("Server forced to shutdown: %v", err) + log.Printf("Http server forced to shutdown: %v", err) } - // Wait for active plans to complete - for { - l := plan.NumActivePlans() - if l == 0 { - break - } - log.Printf("Waiting for %d active plans to finish...\n", l) - time.Sleep(1 * time.Second) + // Execute shutdown hooks + for _, hook := range shutdownHooks { + hook() } log.Println("Shutdown complete") + os.Exit(0) +} + +func waitForActivePlans() chan struct{} { + done := make(chan struct{}) + go func() { + for { + if plan.NumActivePlans() == 0 { + close(done) + return + } + time.Sleep(1 * time.Second) + } + }() + return done } diff --git a/app/server/types/trial.go b/app/server/types/trial.go index 08fed988..396b5a47 100644 --- a/app/server/types/trial.go +++ b/app/server/types/trial.go @@ -1,4 +1,4 @@ package types -const TrialMaxReplies = 10 -const TrialMaxPlans = 0 +const TrialMaxReplies = 15 +const TrialMaxPlans = 10