From 63cf7f8492493ea4df794f6c8a2840f3b2320e5b Mon Sep 17 00:00:00 2001 From: Nandgopal-R Date: Wed, 7 Jan 2026 03:04:51 +0530 Subject: [PATCH 1/4] feat: add query for hospitality analytics --- .../annalytics-for-accommodation-panel.sql.go | 76 +++++++++++++++++++ .../annalytics-for-accommodation-panel.sql | 15 ++++ 2 files changed, 91 insertions(+) create mode 100644 db/gen/annalytics-for-accommodation-panel.sql.go create mode 100644 db/queries/annalytics-for-accommodation-panel.sql diff --git a/db/gen/annalytics-for-accommodation-panel.sql.go b/db/gen/annalytics-for-accommodation-panel.sql.go new file mode 100644 index 00000000..a19c891d --- /dev/null +++ b/db/gen/annalytics-for-accommodation-panel.sql.go @@ -0,0 +1,76 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: annalytics-for-accommodation-panel.sql + +package db + +import ( + "context" + "encoding/json" + + "github.com/jackc/pgx/v5/pgtype" +) + +const getInsideCampusAnalyticsQuery = `-- name: GetInsideCampusAnalyticsQuery :many +SELECT + logged_at::date AS date, + jsonb_build_object( + 'IN', COUNT(*) FILTER (WHERE direction = 'IN'), + 'OUT', COUNT(*) FILTER (WHERE direction = 'OUT') + ) AS counts +FROM gate_management +GROUP BY date +ORDER BY date +` + +type GetInsideCampusAnalyticsQueryRow struct { + Date pgtype.Date `json:"date"` + Counts json.RawMessage `json:"counts"` +} + +func (q *Queries) GetInsideCampusAnalyticsQuery(ctx context.Context, db DBTX) ([]GetInsideCampusAnalyticsQueryRow, error) { + rows, err := db.Query(ctx, getInsideCampusAnalyticsQuery) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetInsideCampusAnalyticsQueryRow + for rows.Next() { + var i GetInsideCampusAnalyticsQueryRow + if err := rows.Scan(&i.Date, &i.Counts); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getLiveBedsAnalyticsQuery = `-- name: GetLiveBedsAnalyticsQuery :many +SELECT + room_filled +FROM hostel_metadata +` + +func (q *Queries) GetLiveBedsAnalyticsQuery(ctx context.Context, db DBTX) ([]int32, error) { + rows, err := db.Query(ctx, getLiveBedsAnalyticsQuery) + if err != nil { + return nil, err + } + defer rows.Close() + var items []int32 + for rows.Next() { + var room_filled int32 + if err := rows.Scan(&room_filled); err != nil { + return nil, err + } + items = append(items, room_filled) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/db/queries/annalytics-for-accommodation-panel.sql b/db/queries/annalytics-for-accommodation-panel.sql new file mode 100644 index 00000000..e7b40e01 --- /dev/null +++ b/db/queries/annalytics-for-accommodation-panel.sql @@ -0,0 +1,15 @@ +-- name: GetInsideCampusAnalyticsQuery :many +SELECT + logged_at::date AS date, + jsonb_build_object( + 'IN', COUNT(*) FILTER (WHERE direction = 'IN'), + 'OUT', COUNT(*) FILTER (WHERE direction = 'OUT') + ) AS counts +FROM gate_management +GROUP BY date +ORDER BY date; + +-- name: GetLiveBedsAnalyticsQuery :many +SELECT + room_filled +FROM hostel_metadata; From 11f2cee9f9768d58845906ec8805c4747bcaa428 Mon Sep 17 00:00:00 2001 From: Nandgopal-R Date: Wed, 7 Jan 2026 03:05:27 +0530 Subject: [PATCH 2/4] feat: add GetInsideCampulAnalytics controller --- api/analytics/hospitality.controller.go | 40 +++++++++++++++++++ api/analytics/routes.go | 3 ++ .../GetInsideCampusAnalytics.bru | 15 +++++++ bruno/hospitalityAnalytics/folder.bru | 8 ++++ 4 files changed, 66 insertions(+) create mode 100644 api/analytics/hospitality.controller.go create mode 100644 bruno/hospitalityAnalytics/GetInsideCampusAnalytics.bru create mode 100644 bruno/hospitalityAnalytics/folder.bru diff --git a/api/analytics/hospitality.controller.go b/api/analytics/hospitality.controller.go new file mode 100644 index 00000000..f3db219f --- /dev/null +++ b/api/analytics/hospitality.controller.go @@ -0,0 +1,40 @@ +package api + +import ( + "context" + "net/http" + "time" + + "github.com/Thanus-Kumaar/anokha-2025-backend/cmd" + db "github.com/Thanus-Kumaar/anokha-2025-backend/db/gen" + "github.com/Thanus-Kumaar/anokha-2025-backend/pkg" + "github.com/gin-gonic/gin" +) + +func GetInsideCampusAnalytics(c *gin.Context) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + conn, err := cmd.DBPool.Acquire(ctx) + if pkg.HandleDbAcquireErr(c, err, "ANALYTICS") { + return + } + defer conn.Release() + + q := db.New() + + insideCampusSummary, err := q.GetInsideCampusAnalyticsQuery(ctx, conn) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "message": "Oops! Something happened. Please try again later", + }) + pkg.Log.ErrorCtx(c, "[ANALYTICS-ERROR]: Failed to get inside campus summary", err) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "Successfully fetched inside campus analytics", + "inside_campus_summary": insideCampusSummary, + }) + pkg.Log.SuccessCtx(c) +} diff --git a/api/analytics/routes.go b/api/analytics/routes.go index 0b10346a..06443f83 100644 --- a/api/analytics/routes.go +++ b/api/analytics/routes.go @@ -11,4 +11,7 @@ func AnalyticsRoutes(r *gin.RouterGroup) { r.GET("/registrations", mw.Auth, mw.CheckAdmin, GetEventRegistrationAnalytics) // r.GET("/people", mw.Auth, mw.CheckAdmin, GetPeopleAnalytics) r.GET("/transactions", mw.Auth, mw.CheckAdmin, GetTransactionAnalytics) + + // Hospitality Analytics + r.GET("/hospitality/inside", GetInsideCampusAnalytics) } diff --git a/bruno/hospitalityAnalytics/GetInsideCampusAnalytics.bru b/bruno/hospitalityAnalytics/GetInsideCampusAnalytics.bru new file mode 100644 index 00000000..c021cb64 --- /dev/null +++ b/bruno/hospitalityAnalytics/GetInsideCampusAnalytics.bru @@ -0,0 +1,15 @@ +meta { + name: GetInsideCampusAnalytics + type: http + seq: 1 +} + +get { + url: {{baseUrl + body: none + auth: inherit +} + +settings { + encodeUrl: true +} diff --git a/bruno/hospitalityAnalytics/folder.bru b/bruno/hospitalityAnalytics/folder.bru new file mode 100644 index 00000000..2e244677 --- /dev/null +++ b/bruno/hospitalityAnalytics/folder.bru @@ -0,0 +1,8 @@ +meta { + name: hospitalityAnalytics + seq: 18 +} + +auth { + mode: inherit +} From e26dbee85fbe84d51e7e1dcffaa42d82ab8ba448 Mon Sep 17 00:00:00 2001 From: Nandgopal-R Date: Wed, 7 Jan 2026 03:39:31 +0530 Subject: [PATCH 3/4] feat: add query for bed analytics --- .../annalytics-for-accommodation-panel.sql.go | 26 +++++++++++++------ .../annalytics-for-accommodation-panel.sql | 17 +++++++++--- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/db/gen/annalytics-for-accommodation-panel.sql.go b/db/gen/annalytics-for-accommodation-panel.sql.go index a19c891d..c2e80978 100644 --- a/db/gen/annalytics-for-accommodation-panel.sql.go +++ b/db/gen/annalytics-for-accommodation-panel.sql.go @@ -17,7 +17,8 @@ SELECT logged_at::date AS date, jsonb_build_object( 'IN', COUNT(*) FILTER (WHERE direction = 'IN'), - 'OUT', COUNT(*) FILTER (WHERE direction = 'OUT') + 'OUT', COUNT(*) FILTER (WHERE direction = 'OUT'), + 'CURRENTLY_INSIDE', COUNT(*) FILTER (WHERE direction = 'IN') - COUNT(*) FILTER (WHERE direction = 'OUT') ) AS counts FROM gate_management GROUP BY date @@ -50,24 +51,33 @@ func (q *Queries) GetInsideCampusAnalyticsQuery(ctx context.Context, db DBTX) ([ } const getLiveBedsAnalyticsQuery = `-- name: GetLiveBedsAnalyticsQuery :many -SELECT - room_filled +SELECT jsonb_build_object( + 'hostels', jsonb_agg( + jsonb_build_object( + 'id', id, + 'hostel_name', hostel_name, + 'room_count', room_count, + 'room_filled', room_filled + ) + ), + 'total_beds_filled', SUM(room_filled) +) FROM hostel_metadata ` -func (q *Queries) GetLiveBedsAnalyticsQuery(ctx context.Context, db DBTX) ([]int32, error) { +func (q *Queries) GetLiveBedsAnalyticsQuery(ctx context.Context, db DBTX) ([]json.RawMessage, error) { rows, err := db.Query(ctx, getLiveBedsAnalyticsQuery) if err != nil { return nil, err } defer rows.Close() - var items []int32 + var items []json.RawMessage for rows.Next() { - var room_filled int32 - if err := rows.Scan(&room_filled); err != nil { + var jsonb_build_object json.RawMessage + if err := rows.Scan(&jsonb_build_object); err != nil { return nil, err } - items = append(items, room_filled) + items = append(items, jsonb_build_object) } if err := rows.Err(); err != nil { return nil, err diff --git a/db/queries/annalytics-for-accommodation-panel.sql b/db/queries/annalytics-for-accommodation-panel.sql index e7b40e01..b0432005 100644 --- a/db/queries/annalytics-for-accommodation-panel.sql +++ b/db/queries/annalytics-for-accommodation-panel.sql @@ -3,13 +3,24 @@ SELECT logged_at::date AS date, jsonb_build_object( 'IN', COUNT(*) FILTER (WHERE direction = 'IN'), - 'OUT', COUNT(*) FILTER (WHERE direction = 'OUT') + 'OUT', COUNT(*) FILTER (WHERE direction = 'OUT'), + 'CURRENTLY_INSIDE', COUNT(*) FILTER (WHERE direction = 'IN') - COUNT(*) FILTER (WHERE direction = 'OUT') ) AS counts FROM gate_management GROUP BY date ORDER BY date; -- name: GetLiveBedsAnalyticsQuery :many -SELECT - room_filled +SELECT jsonb_build_object( + 'hostels', jsonb_agg( + jsonb_build_object( + 'id', id, + 'hostel_name', hostel_name, + 'room_count', room_count, + 'room_filled', room_filled + ) + ), + 'total_beds_filled', SUM(room_filled) +) FROM hostel_metadata; + From 74dbee66b9d2869ac601654230c325c0a7ca84df Mon Sep 17 00:00:00 2001 From: Nandgopal-R Date: Wed, 7 Jan 2026 03:41:11 +0530 Subject: [PATCH 4/4] feat: add controller for bed analytics --- api/analytics/hospitality.controller.go | 28 +++++++++++++++++++ api/analytics/routes.go | 3 +- .../GetInsideCampusAnalytics.bru | 3 +- .../GetLiveBedsAnalytics.bru | 16 +++++++++++ 4 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 bruno/hospitalityAnalytics/GetLiveBedsAnalytics.bru diff --git a/api/analytics/hospitality.controller.go b/api/analytics/hospitality.controller.go index f3db219f..3619fcdc 100644 --- a/api/analytics/hospitality.controller.go +++ b/api/analytics/hospitality.controller.go @@ -38,3 +38,31 @@ func GetInsideCampusAnalytics(c *gin.Context) { }) pkg.Log.SuccessCtx(c) } + +func GetLiveBedsAnalytics(c *gin.Context) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + conn, err := cmd.DBPool.Acquire(ctx) + if pkg.HandleDbAcquireErr(c, err, "ANALYTICS") { + return + } + defer conn.Release() + + q := db.New() + + liveBedsSummary, err := q.GetLiveBedsAnalyticsQuery(ctx, conn) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "message": "Oops! Something happened. Please try again later", + }) + pkg.Log.ErrorCtx(c, "[ANALYTICS-ERROR]: Failed to get live beds summary", err) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "Successfully fetched live beds analytics", + "live_beds_summary": liveBedsSummary, + }) + pkg.Log.SuccessCtx(c) +} diff --git a/api/analytics/routes.go b/api/analytics/routes.go index 06443f83..04c5e6ce 100644 --- a/api/analytics/routes.go +++ b/api/analytics/routes.go @@ -13,5 +13,6 @@ func AnalyticsRoutes(r *gin.RouterGroup) { r.GET("/transactions", mw.Auth, mw.CheckAdmin, GetTransactionAnalytics) // Hospitality Analytics - r.GET("/hospitality/inside", GetInsideCampusAnalytics) + r.GET("/hospitality/inside", mw.Auth, mw.CheckHospitality, GetInsideCampusAnalytics) + r.GET("/hospitality/beds", mw.Auth, mw.CheckHospitality, GetLiveBedsAnalytics) } diff --git a/bruno/hospitalityAnalytics/GetInsideCampusAnalytics.bru b/bruno/hospitalityAnalytics/GetInsideCampusAnalytics.bru index c021cb64..1fe699bc 100644 --- a/bruno/hospitalityAnalytics/GetInsideCampusAnalytics.bru +++ b/bruno/hospitalityAnalytics/GetInsideCampusAnalytics.bru @@ -5,11 +5,12 @@ meta { } get { - url: {{baseUrl + url: {{baseUrl}}/analytics/hospitality/inside body: none auth: inherit } settings { encodeUrl: true + timeout: 0 } diff --git a/bruno/hospitalityAnalytics/GetLiveBedsAnalytics.bru b/bruno/hospitalityAnalytics/GetLiveBedsAnalytics.bru new file mode 100644 index 00000000..eb8bf045 --- /dev/null +++ b/bruno/hospitalityAnalytics/GetLiveBedsAnalytics.bru @@ -0,0 +1,16 @@ +meta { + name: GetLiveBedsAnalytics + type: http + seq: 2 +} + +get { + url: {{baseUrl}}/analytics/hospitality/beds + body: none + auth: inherit +} + +settings { + encodeUrl: true + timeout: 0 +}