Skip to content

Commit

Permalink
update events pageviews handler
Browse files Browse the repository at this point in the history
  • Loading branch information
negrel committed Jun 4, 2024
1 parent deac25b commit 6772c1c
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 88 deletions.
98 changes: 48 additions & 50 deletions pkg/handlers/events_pageviews.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package handlers

import (
"fmt"
"time"

"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/utils"
"github.com/gofiber/storage"
"github.com/google/uuid"
"github.com/prismelabs/analytics/pkg/event"
"github.com/prismelabs/analytics/pkg/services/eventstore"
"github.com/prismelabs/analytics/pkg/services/ipgeolocator"
"github.com/prismelabs/analytics/pkg/services/saltmanager"
"github.com/prismelabs/analytics/pkg/services/sessionstorage"
"github.com/prismelabs/analytics/pkg/services/uaparser"
"github.com/rs/zerolog"
)
Expand All @@ -23,81 +25,77 @@ func ProvidePostEventsPageViews(
uaParserService uaparser.Service,
ipGeolocatorService ipgeolocator.Service,
saltManagerService saltmanager.Service,
storage storage.Storage,
sessionStorage sessionstorage.Service,
) PostEventsPageview {
return func(c *fiber.Ctx) error {
// Referrer of the POST request, that is the viewed page.
requestReferrer := peekReferrerHeader(c)

referrerUri := event.ReferrerUri{}
pageView := event.PageView{}

// Parse user agent.
userAgent := utils.CopyBytes(c.Request().Header.UserAgent())
pageView.Client = uaParserService.ParseUserAgent(utils.UnsafeString(userAgent))
if pageView.Client.IsBot {
return nil
}

// Event date.
pageView.Timestamp = time.Now().UTC()

err := pageView.PageUri.Parse(requestReferrer)
// Parse referrer URI.
err := referrerUri.Parse(utils.CopyBytes(c.Request().Header.Peek("X-Prisme-Document-Referrer")))
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "invalid Referer or X-Prisme-Referrer")
return fiber.NewError(fiber.StatusBadRequest, "invalid X-Prisme-Document-Referrer")
}

err = pageView.ReferrerUri.Parse(c.Request().Header.Peek("X-Prisme-Document-Referrer"))
// Parse page URI.
err = pageView.PageUri.Parse(utils.CopyBytes(requestReferrer))
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "invalid X-Prisme-Document-Referrer")
return fiber.NewError(fiber.StatusBadRequest, "invalid Referer or X-Prisme-Referrer")
}

// Find country code for given IP.
pageView.CountryCode = ipGeolocatorService.FindCountryCodeForIP(c.IP())

// Compute visitor id.
pageView.VisitorId = computeVisitorId(
userAgent, saltManagerService.DailySalt().Bytes(), []byte(c.IP()),
pageView.PageUri.Host(),
userAgent := c.Request().Header.UserAgent()
visitorId := computeVisitorId(
userAgent, saltManagerService.DailySalt().Bytes(),
utils.UnsafeBytes(c.IP()), pageView.PageUri.Host(),
)

newSession := !equalBytes(pageView.ReferrerUri.Host(), pageView.PageUri.Host())
var session session
newSession := !equalBytes(referrerUri.Host(), pageView.PageUri.Host())
if newSession {
// Compute session ID.
session.id = computeSessionId(&pageView)
session.entryTime = pageView.Timestamp

// Store it.
err := storage.Set(
sessionKey(pageView.VisitorId),
unsafeSessionToBytesCast(&session),
24*time.Hour,
)
sessionUuid, err := uuid.NewV7()
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
return fmt.Errorf("failed to generate session uuid: %w", err)
}
} else {
// Retrieve session.
sessionBytes, err := storage.Get(sessionKey(pageView.VisitorId))
if err != nil {
logger.Err(err).Msg("failed to retrieve session id")
return fiber.NewError(fiber.StatusInternalServerError, "failed to retrieve session id")

pageView.Session = event.Session{
PageUri: &pageView.PageUri,
ReferrerUri: &referrerUri,
Client: uaParserService.ParseUserAgent(utils.UnsafeString(userAgent)),
CountryCode: ipGeolocatorService.FindCountryCodeForIP(c.IP()),
VisitorId: visitorId,
SessionUuid: sessionUuid,
Utm: extractUtmParams(pageView.PageUri.QueryArgs()),
Pageviews: 1,
}
if sessionBytes == nil {
return fiber.NewError(fiber.StatusBadRequest, "entry page missing")
pageView.Timestamp = pageView.Session.SessionTime()

} else { // session should already exists
var ok bool
pageView.Session, ok = sessionStorage.GetSession(visitorId)

// Session not found.
// This can happen if tracking script is not installed on all pages or
// prisme instance was restarted.
if !ok {
return fiber.NewError(fiber.StatusBadRequest, "session not found")
}

session = *unsafeBytesToSessionCast(sessionBytes)
pageView.Session.Pageviews++
pageView.Timestamp = time.Now().UTC()
}

// Add session related fields.
pageView.SessionId = session.id
pageView.EntryTimestamp = session.entryTime
// Update session in storage.
upserted := sessionStorage.UpsertSession(pageView.Session)
if !upserted {
logger.Debug().Msg("session upsert was ignored, pageview ignored")
return nil
}

err = eventStore.StorePageView(c.UserContext(), &pageView)
if err != nil {
logger.Err(err).Msg("failed to store page view event")
return fiber.NewError(fiber.StatusInternalServerError, "failed to store page view event")
return fmt.Errorf("failed to store pageview event: %w", err)
}

return nil
Expand Down
28 changes: 0 additions & 28 deletions pkg/handlers/session.go

This file was deleted.

27 changes: 17 additions & 10 deletions pkg/handlers/utils.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package handlers

import (
"encoding/binary"
"fmt"

"github.com/cespare/xxhash/v2"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/utils"
"github.com/prismelabs/analytics/pkg/event"
"github.com/valyala/fasthttp"
)

func peekReferrerHeader(c *fiber.Ctx) []byte {
Expand Down Expand Up @@ -44,14 +44,21 @@ func xxh3(bytesSlice ...[]byte) uint64 {
return hash.Sum64()
}

func sessionKey(visitorId string) string {
return fmt.Sprintf("session_id[%q]", visitorId)
}
func extractUtmParams(args *fasthttp.Args) event.UtmParams {
utmParams := event.UtmParams{}
if args.Len() == 0 {
return utmParams
}

utmParams.Source = string(args.Peek("utm_source"))
if utmParams.Source == "" {
utmParams.Source = string(args.Peek("ref"))
}

utmParams.Medium = string(args.Peek("utm_medium"))
utmParams.Campaign = string(args.Peek("utm_campaign"))
utmParams.Term = string(args.Peek("utm_term"))
utmParams.Content = string(args.Peek("utm_content"))

func computeSessionId(pageView *event.PageView) uint64 {
return xxh3(
binary.LittleEndian.AppendUint64(nil, uint64(pageView.Timestamp.UnixNano())),
utils.UnsafeBytes(pageView.VisitorId),
pageView.PageUri.Host(),
)
return utmParams
}

0 comments on commit 6772c1c

Please sign in to comment.