package analyticsApi import ( "git.stuve.uni-ulm.de/stuve-it/stuve-it-backend/ldapApi" "git.stuve.uni-ulm.de/stuve-it/stuve-it-backend/logger" "github.com/labstack/echo/v5" "github.com/pocketbase/dbx" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/core" "time" ) // initPageViewCount // // This endpoint is used to retrieve page view counts for each unique path. // It adds the following endpoint to the app: // GET /api/analytics/pageViewCount // // The endpoint expects the following query parameter: // // ?startDate=ISO 8601 formatted date (optional) // // - `startDate`: An optional start date in ISO 8601 format (e.g., "2024-11-12T15:04:05Z"). // If provided, the global count reflects views from this date onward. // If not provided, the global count includes all views. // // Response format: // [ // // { // "id": , // "data": "", // "count": , // "last_30_days_data": [ // { // "date": "YYYY-MM-DDTHH:MM:SSZ", // "count": // }, // ... // ] // }, // ... // // ] // // `last_30_days_data` always provides daily counts for the most recent 30 days regardless of the `startDate` parameter. func initPageViewCount(app *pocketbase.PocketBase, e *core.ServeEvent) { e.Router.GET("/api/analytics/pageViewCount", func(c echo.Context) error { // Check if user is in admin group if err := ldapApi.UserIsInAdminGroup(app, c); err != nil { return err } // Get startDate from query parameter startDateParam := c.QueryParam("startDate") var startDate time.Time var err error if startDateParam != "" { // Parse the startDate, expecting an ISO 8601 format startDate, err = time.Parse(time.RFC3339, startDateParam) if err != nil { return apis.NewBadRequestError("Invalid start date format, expected ISO 8601", err) } } type PageViewData struct { ID int `json:"id"` Data string `json:"data"` Count int `json:"count"` Last30DaysData interface{} `json:"last_30_days_data"` } var result []PageViewData // Execute the query with nested JSON aggregation err = app.Dao().DB(). NewQuery(` SELECT (ROW_NUMBER() OVER()) AS id, view.path AS data, ( SELECT COUNT(id) FROM analyticsPageViews WHERE path = view.path AND created >= {:startDate} ) AS count, ( SELECT json_group_array( json_object( 'date', strftime('%Y-%m-%dT%H:%M:%SZ', created), 'count', daily_count ) ) FROM ( SELECT strftime('%Y-%m-%dT%H:%M:%SZ', created) AS date, COUNT(id) AS daily_count FROM analyticsPageViews WHERE path = view.path AND created >= datetime('now', '-30 days') GROUP BY date ) AS daily_data ) AS last_30_days_data FROM analyticsPageViews view GROUP BY view.path `). Bind(dbx.Params{ "startDate": startDate, }). All(&result) if err != nil { return apis.NewApiError(500, "Failed to query page view data", err) } // Return the final JSON response return c.JSON(200, result) }, apis.ActivityLogger(app)) } // initAggregateCount // // This endpoint is used to aggregate session metadata counts by various categories // (device type, browser name, operating system, user agent, geo country code, and preferred language). // It adds the following endpoint to the app: // GET /api/analytics/sessionCounts // // The endpoint expects the following query parameter: // // ?startDate=ISO 8601 formatted date (optional) // // - `startDate`: An optional start date in ISO 8601 format (e.g., "2024-11-12T15:04:05Z"). // If provided, counts reflect sessions from this date onward. // If not provided, counts include all sessions. // // Response format: // // { // "device_type": [ // { "value": "", "count": }, // ... // ], // "browser_name": [ // { "value": "", "count": }, // ... // ], // "operating_system": [ // { "value": "", "count": }, // ... // ], // "user_agent": [ // { "value": "", "count": }, // ... // ], // "geo_country_code": [ // { "value": "", "count": }, // ... // ], // "preferred_language": [ // { "value": "", "count": }, // ... // ] // } // // Each category includes an array of objects where `value` is the specific item (e.g., a device type or browser name) // and `count` is the number of sessions matching that item since `startDate`. func initAggregateCount(app *pocketbase.PocketBase, e *core.ServeEvent) { e.Router.GET("/api/analytics/aggregateCount", func(c echo.Context) error { // Check if user is in admin group if err := ldapApi.UserIsInAdminGroup(app, c); err != nil { return err } // Parse the start date startDate, err := time.Parse(time.RFC3339, c.QueryParam("startDate")) if err != nil { return apis.NewBadRequestError("Invalid start date format, expected ISO 8601", err) } type Data struct { Value string `db:"value" json:"value"` Count int `db:"count" json:"count"` } // Use a map to store the response data response := make(map[string][]Data) fields := []string{"device_type", "browser_name", "operating_system", "geo_country_code", "preferred_language"} for _, field := range fields { data := []Data{} err := app.Dao().DB(). NewQuery(` SELECT ` + field + ` as value, COUNT(id) AS count FROM analyticsSessions WHERE created >= {:startDate} GROUP BY ` + field). Bind(dbx.Params{ "startDate": startDate, }). All(&data) if err != nil { return apis.NewApiError(500, "Failed to aggregate data for "+field, err) } // Assign the data to the corresponding field in the map response[field] = data } // Return the final JSON response return c.JSON(200, response) }, apis.ActivityLogger(app)) } // InitAnalyticsApi initializes analytics api endpoints func InitAnalyticsApi(app *pocketbase.PocketBase, e *core.ServeEvent) error { logger.LogInfoF("Initializing analytics api") initPageViewCount(app, e) initAggregateCount(app, e) return nil }