Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions deploy/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ services:
API_HOST: 0.0.0.0
API_PORT: 8080
GRPC_PORT: 9090
PUBLIC_API_URL: ${PUBLIC_API_URL:-}
PUBLIC_WEB_URL: ${PUBLIC_WEB_URL:-}
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:8080/health || exit 1"]
interval: 10s
Expand Down
73 changes: 62 additions & 11 deletions internal/api/handlers/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,37 @@ func (h *GitHubHandler) getBaseURLs(r *http.Request) (string, string) {
scheme = "https"
}

// 3. Determine API URL
// 3. Determine host - prefer X-Forwarded-Host over r.Host
// r.Host can be internal container names like "api:8080"
host := r.Host
if forwardHost := r.Header.Get("X-Forwarded-Host"); forwardHost != "" {
host = forwardHost
h.logger.Debug("using X-Forwarded-Host", "host", host)
}

// Filter out internal container hostnames
if strings.HasPrefix(host, "api:") || strings.HasPrefix(host, "web:") ||
strings.HasPrefix(host, "worker:") || strings.HasPrefix(host, "postgres:") {
h.logger.Warn("detected internal container hostname, this will cause issues", "host", host)
// Try to use environment variables or database settings instead
if dbDomain != "" && dbDomain != "localhost" {
host = dbDomain
h.logger.Info("using database server_domain instead", "host", host)
}
}

detectedAPI := fmt.Sprintf("%s://%s", scheme, host)

apiURL := os.Getenv("API_URL")
// 4. Check environment variables
apiURL := os.Getenv("PUBLIC_API_URL")
if apiURL == "" {
apiURL = os.Getenv("API_URL")
}
if apiURL == "" {
apiURL = detectedAPI
}

// 4. Determine Web URL
// 5. Determine Web URL
detectedWeb := detectedAPI
if webOverride := r.URL.Query().Get("web_url"); webOverride != "" {
detectedWeb = webOverride
Expand All @@ -70,12 +88,15 @@ func (h *GitHubHandler) getBaseURLs(r *http.Request) (string, string) {
}
}

webURL := os.Getenv("WEB_URL")
webURL := os.Getenv("PUBLIC_WEB_URL")
if webURL == "" {
webURL = os.Getenv("WEB_URL")
}
if webURL == "" {
webURL = detectedWeb
}

// 5. Override if database setting exists and isn't just "localhost"
// 6. Override if database setting exists and isn't just "localhost"
if dbDomain != "" && dbDomain != "localhost" {
// If it's an IP, use it directly. If it's a domain, use it directly.
// We'll assume the ports are standard or same as detected
Expand All @@ -87,18 +108,19 @@ func (h *GitHubHandler) getBaseURLs(r *http.Request) (string, string) {
webUrlObj, _ := url.Parse(webURL)

apiHost := dbDomain
if apiUrlObj != nil && apiUrlObj.Port() != "" {
if apiUrlObj != nil && apiUrlObj.Port() != "" && apiUrlObj.Port() != "80" && apiUrlObj.Port() != "443" {
apiHost = dbDomain + ":" + apiUrlObj.Port()
}

webHost := dbDomain
if webUrlObj != nil && webUrlObj.Port() != "" {
if webUrlObj != nil && webUrlObj.Port() != "" && webUrlObj.Port() != "80" && webUrlObj.Port() != "443" {
webHost = dbDomain + ":" + webUrlObj.Port()
}

return fmt.Sprintf("%s://%s", scheme, webHost), fmt.Sprintf("%s://%s", scheme, apiHost)
}

h.logger.Info("using detected URLs", "webURL", webURL, "apiURL", apiURL)
return webURL, apiURL
}

Expand Down Expand Up @@ -384,33 +406,62 @@ func (h *GitHubHandler) AppInstall(w http.ResponseWriter, r *http.Request) {
}

// PostInstallation handles the redirect after a user installs the GitHub App.
// This is called by GitHub, so we need to extract user info from the setup_action parameter.
func (h *GitHubHandler) PostInstallation(w http.ResponseWriter, r *http.Request) {
installationIDStr := r.URL.Query().Get("installation_id")
setupAction := r.URL.Query().Get("setup_action")

h.logger.Info("GitHub post-installation callback",
"installation_id", installationIDStr,
"setup_action", setupAction,
)

if installationIDStr == "" {
http.Redirect(w, r, "/git?error=missing_installation_id", http.StatusFound)
webURL, _ := h.getBaseURLs(r)
http.Redirect(w, r, webURL+"/git?error=missing_installation_id", http.StatusFound)
return
}

installationID, _ := strconv.ParseInt(installationIDStr, 10, 64)

// Try to get userID from context (if logged in)
userID := middleware.GetUserID(r.Context())

// If no user in context, we need to handle this differently
// Store the installation without a user association initially
// The user will be associated when they first access the Git page while logged in
if userID == "" {
h.logger.Info("no user context in post-installation, storing orphaned installation", "installation_id", installationID)
// We could store this in a temporary table or skip for now
// For now, just redirect to login
webURL, _ := h.getBaseURLs(r)
http.Redirect(w, r, webURL+"/git?success=GitHub+App+installed&installation_id="+installationIDStr, http.StatusFound)
return
}

// Save the installation
// Note: In a real app we'd fetch installation details from GitHub first
inst := &models.GitHubInstallation{
ID: installationID,
UserID: userID,
AccountLogin: "Searching...", // Will be updated on first repo list/webhook
AccountLogin: "Pending...", // Will be updated on first repo list/webhook
}

if err := h.store.GitHub().CreateInstallation(r.Context(), inst); err != nil {
h.logger.Error("failed to create installation", "error", err)
}

webURL, _ := h.getBaseURLs(r)

http.Redirect(w, r, webURL+"/git?success=GitHub+App+installed", http.StatusFound)
}

// Webhook handles GitHub webhook events.
func (h *GitHubHandler) Webhook(w http.ResponseWriter, r *http.Request) {
// TODO: Implement webhook handling
h.logger.Info("GitHub webhook received", "event", r.Header.Get("X-GitHub-Event"))
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"ok"}`))
}

// ListRepos lists repositories for the authenticated user from both App installations and OAuth accounts.
func (h *GitHubHandler) ListRepos(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
Expand Down
6 changes: 6 additions & 0 deletions internal/api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ func (s *Server) setupRouter() {
githubHandler := handlers.NewGitHubHandler(s.store, s.logger)
r.Get("/github/callback", githubHandler.ManifestCallback)
r.Get("/github/oauth/callback", githubHandler.OAuthCallback)

// GitHub post-installation (public - called by GitHub after app install)
r.Route("/v1/github", func(r chi.Router) {
r.Get("/post-install", githubHandler.PostInstallation)
r.Get("/webhook", githubHandler.Webhook)
})

// API v1 routes
r.Route("/v1", func(r chi.Router) {
Expand Down