Skip to content

Conversation

@thomasorgeval
Copy link
Owner

@thomasorgeval thomasorgeval commented Jan 4, 2026

Summary by CodeRabbit

  • New Features

    • Optional analytics added: configurable event tracking for pageviews, outbound links, performance, custom events and errors; can be enabled via environment variables.
  • Documentation

    • README updated to document PORT, required analytics environment variables, updated run example, and list of tracked events.
  • Chores

    • .gitignore updated to exclude editor config directory.
    • Build process simplified to remove native C toolchain requirements.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 4, 2026

Warning

Rate limit exceeded

@thomasorgeval has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 18 minutes and 2 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 935036e and cc8f0c9.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (2)
  • go.mod
  • main.go
📝 Walkthrough

Walkthrough

Adds environment-configured Rybbit analytics (async event emission) to the Go server, updates README to document PORT and RYBBIT variables and events, removes CGO from the Docker build, and adds .idea to .gitignore.

Changes

Cohort / File(s) Summary
IDE Configuration
\.gitignore
Adds .idea to ignore IDE/project files.
Documentation
README.md
Replaces Data Persistence with Environment Variables; documents PORT (default 3000), RYBBIT env vars (RYBBIT_API_KEY, RYBBIT_API_URL, RYBBIT_SITE_ID), updates docker run example, and lists tracked events (Pageview, Outbound, Error).
Analytics Integration
main.go
Adds Rybbit analytics types (EventType, RybbitEvent, RybbitConfig), constants (EventPageview, EventCustom, EventPerformance, EventOutbound, EventError), global rybbitCfg, initRybbitConfig(), sendRybbitEvent() (async HTTP POST with headers), getClientIP(), and emits events at 404s, API errors, redirects, and outbound success; removes prior DB/geo/cache logic.
Build Configuration
Dockerfile
Removes CGO enablement and native C toolchain install steps from the builder stage; build now runs go build without CGO.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant Handler as Go HTTP Handler
    participant Rybbit as Rybbit API
    participant Logger as Local Logger

    Client->>Handler: HTTP request
    Handler->>Handler: Determine route & metadata (path, referrer, UA)
    alt Analytics enabled (all RYBBIT env vars set)
        rect rgb(235,245,255)
        Handler->>Handler: Build RybbitEvent (type, site, IP, title, etc.)
        Handler->>Rybbit: sendRybbitEvent() (goroutine) POST /events with API key
        Rybbit-->>Handler: async response (200/4xx/5xx)
        Handler->>Logger: Log non-fatal send errors
        end
    else Analytics disabled
        Note over Handler: No event emitted
    end
    Handler-->>Client: HTTP response (non-blocking)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇
I hop on keys and sniff the stream,
Env-set lights, an async dream,
Events I fling, then bound away,
Logs make notes of each small play,
Rybbit hums — metrics in beam.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'Add Rybbit analytics integration and configuration' directly and clearly summarizes the main changes, which center on adding Rybbit Analytics to the codebase across multiple files.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
main.go (4)

38-44: Consider removing unused event type constants.

EventCustom and EventPerformance are defined but never used in the codebase. Consider removing them to reduce dead code, or document their intended future use.


141-146: Reuse http.Client for better connection pooling and resource efficiency.

Creating a new http.Client per event call prevents connection reuse. Consider creating a package-level client (with appropriate timeout) to leverage connection pooling, especially under high request volume.

🔎 Proposed fix

Add a package-level client:

var rybbitClient = &http.Client{Timeout: 5 * time.Second}

Then in sendRybbitEvent:

-		client := &http.Client{Timeout: 5 * time.Second}
-		resp, err := client.Do(req)
+		resp, err := rybbitClient.Do(req)

124-152: Consider bounding concurrent analytics goroutines.

Unbounded goroutine spawning per request could lead to resource exhaustion if the Rybbit API becomes slow or unresponsive. For a low-traffic service this is likely fine, but for production hardening, consider using a buffered channel or worker pool pattern to limit concurrency.


262-280: Use strings package and strip port from RemoteAddr.

Two issues:

  1. Using bytes.IndexByte on a string requires conversion; strings.Index is more idiomatic.
  2. r.RemoteAddr includes the port (e.g., 192.168.1.1:12345), which should be stripped for a clean IP address.
🔎 Proposed fix
+import "strings"
+
 func getClientIP(r *http.Request) string {
 	// Try X-Forwarded-For header first (for proxies/load balancers)
 	if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
 		// X-Forwarded-For can contain multiple IPs, take the first one
-		if idx := bytes.IndexByte([]byte(xff), ','); idx > 0 {
-			return xff[:idx]
+		if idx := strings.Index(xff, ","); idx > 0 {
+			return strings.TrimSpace(xff[:idx])
 		}
-		return xff
+		return strings.TrimSpace(xff)
 	}
 
 	// Try X-Real-IP header
 	if xri := r.Header.Get("X-Real-IP"); xri != "" {
 		return xri
 	}
 
 	// Fall back to RemoteAddr (strip port if present)
-	return r.RemoteAddr
+	if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
+		return host
+	}
+	return r.RemoteAddr
 }

Note: This requires adding "net" to imports and allows removing "bytes" since it's no longer needed.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 761eed5 and ef12b77.

📒 Files selected for processing (3)
  • .gitignore
  • README.md
  • main.go
🔇 Additional comments (5)
.gitignore (1)

1-1: LGTM!

Standard practice to exclude JetBrains IDE configuration directories from version control.

README.md (1)

49-80: LGTM!

Clear and comprehensive documentation for the Rybbit analytics integration. The example Docker command and explanation of required environment variables will help users configure the feature correctly.

main.go (3)

99-111: LGTM!

Good defensive coding with the partial configuration warning. This will help users debug misconfigurations.


169-178: Verify privacy compliance for sending IP addresses to analytics.

IP addresses are considered personal data under GDPR and similar regulations. Sending raw IP addresses to a third-party analytics service may require user consent or a legitimate interest basis. Consider whether IP anonymization or hashing would be more appropriate for your compliance requirements.


246-259: LGTM!

Good use of EventOutbound for tracking successful redirects, and including the trip slug in PageTitle provides useful context in analytics.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
main.go (3)

3-17: Critical: Missing database/sql import.

The code uses db variable (lines 89, 234) and database operations, and imports the SQLite3 driver at line 16, but the database/sql package import is missing. This will cause a compilation error.

🔎 Proposed fix
 import (
 	"bytes"
+	"database/sql"
 	"encoding/json"
 	"fmt"
 	"gopkg.in/yaml.v3"

240-248: Missing cache invalidation mechanism.

The cache is populated at line 311 and checked at lines 240-248, but there's no cache invalidation or TTL mechanism. The summary mentions removing startCacheResetter, but without any cache invalidation, stale entries could persist indefinitely. This may cause redirects to outdated trips when:

  • An ongoing trip ends and a new one begins
  • The user's trip list changes

Consider implementing a TTL-based cache expiration or periodic cache reset.

💡 Example implementation with TTL
type cacheEntry struct {
	url       string
	expiresAt time.Time
}

var cache = struct {
	sync.RWMutex
	store map[string]cacheEntry
}{store: make(map[string]cacheEntry)}

// When checking cache:
if found && time.Now().Before(entry.expiresAt) {
	// use cached value
}

// When setting cache:
cache.store[host] = cacheEntry{
	url:       target,
	expiresAt: time.Now().Add(1 * time.Hour), // or your preferred TTL
}

Also applies to: 310-312


197-326: Add GDPR/privacy compliance documentation and user consent mechanism.

The application collects and transmits IP addresses (personal data under GDPR) to both a local database and an external Rybbit Analytics API, including Polarsteps usernames and other identifiers. This requires:

  • A privacy policy documenting data collection, purpose, and retention
  • Explicit user consent before collecting IP-based geolocation and analytics data
  • Data Processing Agreements with external processors (Rybbit)
  • A mechanism to allow users to opt out of analytics collection

Ensure compliance with GDPR Article 6 (lawful basis), Article 13 (transparency), and Article 28 (data processor agreements).

🧹 Nitpick comments (4)
main.go (4)

45-77: LGTM with optional observation.

The analytics types and constants are well-structured. Note that EventCustom and EventPerformance constants are defined but not currently used, and some RybbitEvent fields (like ScreenWidth, ScreenHeight) are never populated. This is acceptable for future extensibility or API compatibility.


135-175: Consider adding rate limiting for analytics requests.

The async implementation with timeout and error handling is solid. However, there's no rate limiting on outbound analytics requests. During traffic spikes, this could spawn many concurrent goroutines making HTTP requests to the Rybbit API.

Consider implementing a worker pool pattern or semaphore to limit concurrent analytics requests.

💡 Example implementation with a semaphore
// At package level
var analyticsSemaphore = make(chan struct{}, 10) // max 10 concurrent requests

// In sendRybbitEvent, wrap the goroutine
go func() {
	analyticsSemaphore <- struct{}{} // acquire
	defer func() { <-analyticsSemaphore }() // release
	
	payload, err := json.Marshal(event)
	// ... rest of the implementation
}()

275-283: Refactor duplicate profile redirect tracking.

Lines 275-283 and 294-302 contain identical code for tracking profile redirects. Consider extracting this into a helper function to reduce duplication.

🔎 Proposed refactor
func trackProfileRedirect(r *http.Request, host, username string) {
	sendRybbitEvent(RybbitEvent{
		Type:      EventPageview,
		Hostname:  host,
		Pathname:  r.URL.Path,
		UserAgent: r.Header.Get("User-Agent"),
		IPAddress: getClientIP(r),
		Referrer:  r.Header.Get("Referer"),
		UserID:    username,
	})
}

// Then use it at both locations:
trackProfileRedirect(r, host, username)

Also applies to: 294-302


275-283: Reconsider event type for profile redirects.

Profile redirects (lines 272-286 and 289-305) are tracked as EventPageview, but they're actually external redirects to Polarsteps profile pages, similar to trip redirects. Consider using EventOutbound for consistency with the successful trip redirect at lines 317-326.

Also applies to: 294-302

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ef12b77 and 684de75.

📒 Files selected for processing (3)
  • .gitignore
  • README.md
  • main.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build and Push Docker Image
🔇 Additional comments (7)
.gitignore (1)

1-1: LGTM!

Standard practice to ignore JetBrains IDE configuration files.

README.md (1)

49-80: LGTM!

The documentation clearly explains the environment variables, the requirement for all three Rybbit variables to be set, and the types of events tracked. The example docker command is well-structured and illustrative.

main.go (5)

80-80: LGTM!

Using a package-level variable for configuration initialized at startup is a standard pattern.


104-117: LGTM!

Analytics initialization is correctly placed after configuration loading and before starting the HTTP server. The logging provides good visibility into the analytics status.


121-133: LGTM!

The all-or-nothing configuration approach is sound, and the warning for partial configuration helps prevent misconfiguration issues.


331-349: LGTM!

The client IP extraction logic correctly handles proxy headers with appropriate precedence (X-Forwarded-For → X-Real-IP → RemoteAddr). The idx > 0 check at line 336 defensively prevents returning an empty string if the header is malformed.


7-7: No security advisories affect these versions. gopkg.in/yaml.v3 v3.0.1 is the fixed release for CVE-2022-28948, and github.com/mattn/go-sqlite3 v1.14.32 is secure with no known CVEs.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
main.go (1)

90-90: Critical: Undefined variable db causing build failure.

The pipeline failure reports "undefined: db" at this line. The variable db is being assigned but never declared. Add a package-level declaration for db to fix this build error.

🔎 Add package-level db declaration

Add this declaration near the other package-level variables (after line 81):

var db *sql.DB

And ensure database/sql is imported at the top of the file.

🧹 Nitpick comments (3)
main.go (3)

137-175: LGTM! Async event emission is well-implemented.

The function correctly sends events asynchronously to avoid blocking responses, includes proper error handling, and uses the configured HTTP client with timeout.

Optional: Consider adding concurrency control.

Under high traffic, spawning unbounded goroutines could consume resources. Consider adding a worker pool or rate limiter if you expect high request volumes, though the current implementation is acceptable for typical redirect service traffic.


211-217: Refactor to use getClientIP helper consistently.

This IP extraction logic duplicates the more comprehensive getClientIP function (lines 332-352). The existing code doesn't check the X-Real-IP header, which getClientIP handles.

🔎 Use getClientIP helper
-	ip := r.Header.Get("X-Forwarded-For")
-	if ip == "" {
-		ip = r.RemoteAddr
-	}
-	if realIP, _, err := net.SplitHostPort(ip); err == nil {
-		ip = realIP
-	}
+	ip := getClientIP(r)

188-188: Consider translating comment to English for consistency.

This French comment ("Remove the www. prefix if present") is inconsistent with the English comments used throughout the rest of the codebase.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 684de75 and 935036e.

📒 Files selected for processing (2)
  • Dockerfile
  • main.go
🧰 Additional context used
🪛 GitHub Actions: Build and Push Docker Image
main.go

[error] 90-90: undefined: db

🔇 Additional comments (8)
main.go (7)

45-77: LGTM! Well-structured analytics types.

The event types, RybbitEvent structure, and RybbitConfig are well-defined with appropriate JSON tags using omitempty for optional fields.


123-134: LGTM! Solid configuration initialization.

The function correctly requires all three environment variables to enable analytics and provides a helpful warning when partially configured.


193-209: LGTM! Error tracking for unknown hosts.

The analytics event correctly tracks 404 errors with appropriate metadata.


252-269: LGTM! Proper error tracking and fallback.

The analytics event correctly tracks API errors and the code appropriately falls back to redirecting to the user's profile page.


271-306: LGTM! Profile redirect tracking implemented.

The pageview events correctly track scenarios where users are redirected to their profile page. The use of EventPageview is reasonable for these cases.


316-329: LGTM! Outbound link tracking implemented correctly.

The analytics event properly tracks successful redirects with relevant metadata including the trip slug in the page title.


332-352: LGTM! Robust IP extraction logic.

The function correctly handles multiple headers with proper fallback order and correctly extracts the first IP from comma-separated X-Forwarded-For values.

Dockerfile (1)

6-6: No changes required – comment addresses non-existent SQLite3 dependency.

This codebase uses only the Go standard library and gopkg.in/yaml.v3. There is no go-sqlite3 dependency, no CGO requirement, and no database-related code. The application is a pure-Go HTTP redirector that reads domain configuration from YAML and fetches trip data from an external API. The Dockerfile is correctly configured for a pure-Go build without CGO.

Likely an incorrect or invalid review comment.

@thomasorgeval thomasorgeval merged commit 3630d14 into main Jan 4, 2026
2 checks passed
@thomasorgeval thomasorgeval deleted the feat/rybbit-analytics branch January 4, 2026 13:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant