From 32df254ef74e7d4f1c7a436753104f1c1a3ac1cf Mon Sep 17 00:00:00 2001 From: Ritesh Koushik Date: Wed, 7 Jan 2026 00:47:58 +0530 Subject: [PATCH 01/12] feat: Rollout new status controllers --- api/accomodation/gate.controllers.go | 160 +++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/api/accomodation/gate.controllers.go b/api/accomodation/gate.controllers.go index e88f55c0..c2109fa8 100644 --- a/api/accomodation/gate.controllers.go +++ b/api/accomodation/gate.controllers.go @@ -356,3 +356,163 @@ func GateStatus(c *gin.Context) { }) pkg.Log.SuccessCtx(c) } + +// If No Accomodation, then day scholar +// - Everyday One CheckIn. +// +// If Accomodation +// - LifeTime One CheckIn +func GateCheckInStatus(c *gin.Context) { + hospId := c.Param("hospId") + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + conn, err := cmd.DBPool.Acquire(ctx) + if pkg.HandleDbAcquireErr(c, err, "GATE-CHECKIN-STATUS") { + return + } + defer conn.Release() + + q := db.New() + res, err := q.GateCheckStatusQuery(ctx, conn, pgtype.Text{ + String: hospId, + Valid: true, + }) + if err == pgx.ErrNoRows { + c.JSON(http.StatusNotFound, gin.H{ + "message": "Invalid hospitality ID", + "allow": false, + "reason": "Invalid hospitality ID provided.", + }) + pkg.Log.WarnCtx(c, "[GATE-CHECKIN-WARN]: Invalid hospitality ID") + return + } + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "message": "Oops! Something happened. Please try again later", + "allow": false, + "reason": "Server error.", + }) + pkg.Log.ErrorCtx(c, "[GATE-CHECKIN-ERROR]: Failed to check gate status", err) + return + } + + // Type Assertions to handle interface issues + lastCheckIn, lastCheckInOK := res.LastCheckIn.(pgtype.Timestamp) + lastCheckOut, lastCheckOutOK := res.LastCheckOut.(pgtype.Timestamp) + + // hasAccomodation would be true if payment_status is not NULL or empty + hasAccomodation := res.AccomodationStatus.Valid && res.AccomodationStatus.String != "" + + if hasAccomodation { + // Rule: Cannot check-in if already inside + isValidCheckIn := lastCheckInOK && lastCheckIn.Valid + isValidCheckOut := (!lastCheckOutOK || !lastCheckOut.Valid || lastCheckIn.Time.After(lastCheckOut.Time)) + isAlreadyInside := isValidCheckIn && isValidCheckOut + + if isAlreadyInside { + // Rule: Cannot check-in if already inside + // Already inside is defined as last_check_in > last_check_out + c.JSON(http.StatusOK, gin.H{ + "message": "Check-in status", + "allow": false, + "reason": "Already check-in.", + }) + pkg.Log.InfoCtx(c, "[GATE-CHECKIN-STATUS]: Denied check-in for accomodation holder (already inside)") + return + } + // If not inside then fall through to success case + } else { // does not have accomodation + // Rule: Day scholars can check-in once per day + if lastCheckIn.Valid { + now := time.Now() + lastCheckInTime := lastCheckIn.Time + + if lastCheckInTime.Year() == now.Year() && lastCheckInTime.YearDay() == now.YearDay() { + c.JSON(http.StatusOK, gin.H{ + "message": "Check-in status", + "allow": false, + "reason": "Already checked in today", + }) + pkg.Log.InfoCtx(c, "[GATE-CHECKIN-STATUS]: Denied check-in for day scholar (already checked in today)") + return + } + } + } + + c.JSON(http.StatusOK, gin.H{ + "message": "Checkin status sent successfully", + "allow": true, + "reason": "", + }) + pkg.Log.SuccessCtx(c) +} + +// If No Accomodation, then day scholar +// - Everyday One CheckOut +// +// If Accomodation +// - LifeTime One CheckOut +func GateCheckOutStatus(c *gin.Context) { + hospId := c.Param("hospId") + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + conn, err := cmd.DBPool.Acquire(ctx) + if pkg.HandleDbAcquireErr(c, err, "GATE-CHECKOUT-STATUS") { + return + } + defer conn.Release() + + q := db.New() + + res, err := q.GateCheckStatusQuery(ctx, conn, pgtype.Text{ + String: hospId, + Valid: true, + }) + if err == pgx.ErrNoRows { + c.JSON(http.StatusNotFound, gin.H{ + "message": "Invalid hospitality ID", + "allow": false, + "reason": "Invalid hospitality ID provided", + }) + pkg.Log.WarnCtx(c, "[GATE-CHECKOUT-WARN]: Invalid hospitality ID") + return + } + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "message": "Oops! Something happened. Please try again later", + }) + pkg.Log.ErrorCtx(c, "[GATE-CHECKOUT-ERROR]: Failed to check gate status", err) + return + } + + // Type assertions + lastCheckIn, lastCheckInOK := res.LastCheckIn.(pgtype.Timestamp) + lastCheckOut, lastCheckOutOK := res.LastCheckIn.(pgtype.Timestamp) + + // A user can checkout only if they are currently checked in. A user is + // inside if their last checkin is more recent that their last checkout + isValidCheckIn := lastCheckInOK && lastCheckIn.Valid + isValidCheckOut := (!lastCheckOutOK || !lastCheckOut.Valid || !lastCheckIn.Time.After(lastCheckOut.Time)) + isCurrentlyInside := isValidCheckIn && isValidCheckOut + + if !isCurrentlyInside { + c.JSON(http.StatusOK, gin.H{ + "message": "Checkout status", + "allow": false, + "reason": "Not currently checkin in.", + }) + pkg.Log.InfoCtx(c, "[GATE-CHECKOUT-STATUS]: Denied checkout (not inside)") + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "Checkout status sent successfully", + "allow": true, + "reason": "", + }) + pkg.Log.SuccessCtx(c) +} From 8503a465afcd2a0fac8096abcba1c217db79da49 Mon Sep 17 00:00:00 2001 From: Ritesh Koushik Date: Wed, 7 Jan 2026 00:48:06 +0530 Subject: [PATCH 02/12] fix: gateCheckStatusQuery --- db/gen/gate-management-for-app.sql.go | 33 ++++++++++++++++++++++++++ db/queries/gate-management-for-app.sql | 19 +++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/db/gen/gate-management-for-app.sql.go b/db/gen/gate-management-for-app.sql.go index d5bcda89..cc8cd484 100644 --- a/db/gen/gate-management-for-app.sql.go +++ b/db/gen/gate-management-for-app.sql.go @@ -81,6 +81,39 @@ func (q *Queries) GateCheckInOutQuery(ctx context.Context, db DBTX, arg GateChec return direction, err } +const gateCheckStatusQuery = `-- name: GateCheckStatusQuery :one +SELECT + ad.payment_status AS accomodation_status, + ( + SELECT MAX(logged_out) + FROM gate_management + WHERE student_id = s.id AND direction = 'IN' + ) AS last_check_in, + ( + SELECT MAX(logged_out) + FROM gate_management + WHERE + student_id = s.id AND direction = 'OUT' + ) AS last_check_out +FROM student s +LEFT JOIN accomodation_details ad ON s.id = ad.student_id +WHERE + s.hospitality_id = $1 +` + +type GateCheckStatusQueryRow struct { + AccomodationStatus pgtype.Text `json:"accomodation_status"` + LastCheckIn interface{} `json:"last_check_in"` + LastCheckOut interface{} `json:"last_check_out"` +} + +func (q *Queries) GateCheckStatusQuery(ctx context.Context, db DBTX, hospitalityID pgtype.Text) (GateCheckStatusQueryRow, error) { + row := db.QueryRow(ctx, gateCheckStatusQuery, hospitalityID) + var i GateCheckStatusQueryRow + err := row.Scan(&i.AccomodationStatus, &i.LastCheckIn, &i.LastCheckOut) + return i, err +} + const hostelCheckInQuery = `-- name: HostelCheckInQuery :one WITH student_lookup AS ( SELECT id diff --git a/db/queries/gate-management-for-app.sql b/db/queries/gate-management-for-app.sql index 28961274..07062bc6 100644 --- a/db/queries/gate-management-for-app.sql +++ b/db/queries/gate-management-for-app.sql @@ -89,3 +89,22 @@ SELECT (SELECT logged_at FROM last_check_in) AS last_check_in, (SELECT logged_at FROM last_check_out) AS last_check_out FROM student_info si; + +-- name: GateCheckStatusQuery :one +SELECT + ad.payment_status AS accomodation_status, + ( + SELECT MAX(logged_out) + FROM gate_management + WHERE student_id = s.id AND direction = 'IN' + ) AS last_check_in, + ( + SELECT MAX(logged_out) + FROM gate_management + WHERE + student_id = s.id AND direction = 'OUT' + ) AS last_check_out +FROM student s +LEFT JOIN accomodation_details ad ON s.id = ad.student_id +WHERE + s.hospitality_id = $1; From d9596290df4b7060a14e0d01aabbfa060cbd569e Mon Sep 17 00:00:00 2001 From: Ritesh Koushik Date: Wed, 7 Jan 2026 00:50:59 +0530 Subject: [PATCH 03/12] fix: Rollout routes --- api/accomodation/routes.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/accomodation/routes.go b/api/accomodation/routes.go index 488f8586..1395f371 100644 --- a/api/accomodation/routes.go +++ b/api/accomodation/routes.go @@ -45,8 +45,9 @@ func GateRoutes(r *gin.RouterGroup) { r.GET("/app/:accId", mw.Auth, mw.CheckHospitality, mw.CheckGate, GetAccommodationById) r.PUT("/app/:accId", mw.Auth, mw.CheckHospitality, mw.CheckGate, UpdateAccommodationById) + r.GET("/app/gate/status/:hospId", mw.Auth, mw.CheckHospitality, mw.CheckGate, GateCheckInStatus) r.POST("/app/gate/check-in/:hospId", mw.Auth, mw.CheckHospitality, mw.CheckGate, GateCheckIn) - r.GET("/app/gate/status/:hospId", mw.Auth, mw.CheckHospitality, mw.CheckGate, GateStatus) + r.GET("/app/gate/status/:hospId", mw.Auth, mw.CheckHospitality, mw.CheckGate, GateCheckOutStatus) r.POST("/app/gate/check-out/:hospId", mw.Auth, mw.CheckHospitality, mw.CheckGate, GateCheckOut) r.POST("/app/hostel/check-in/:hospId", mw.Auth, mw.CheckHospitality, mw.CheckHostel, HostelCheckIn) From 7b96d541dd750c0e938fa9ce933bfbcecdc1e9ff Mon Sep 17 00:00:00 2001 From: Ritesh Koushik Date: Wed, 7 Jan 2026 00:51:23 +0530 Subject: [PATCH 04/12] fix --- api/accomodation/routes.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/accomodation/routes.go b/api/accomodation/routes.go index 1395f371..56e9d6d7 100644 --- a/api/accomodation/routes.go +++ b/api/accomodation/routes.go @@ -45,9 +45,9 @@ func GateRoutes(r *gin.RouterGroup) { r.GET("/app/:accId", mw.Auth, mw.CheckHospitality, mw.CheckGate, GetAccommodationById) r.PUT("/app/:accId", mw.Auth, mw.CheckHospitality, mw.CheckGate, UpdateAccommodationById) - r.GET("/app/gate/status/:hospId", mw.Auth, mw.CheckHospitality, mw.CheckGate, GateCheckInStatus) + r.GET("/app/gate/status/check-in/:hospId", mw.Auth, mw.CheckHospitality, mw.CheckGate, GateCheckInStatus) r.POST("/app/gate/check-in/:hospId", mw.Auth, mw.CheckHospitality, mw.CheckGate, GateCheckIn) - r.GET("/app/gate/status/:hospId", mw.Auth, mw.CheckHospitality, mw.CheckGate, GateCheckOutStatus) + r.GET("/app/gate/status/check-out/:hospId", mw.Auth, mw.CheckHospitality, mw.CheckGate, GateCheckOutStatus) r.POST("/app/gate/check-out/:hospId", mw.Auth, mw.CheckHospitality, mw.CheckGate, GateCheckOut) r.POST("/app/hostel/check-in/:hospId", mw.Auth, mw.CheckHospitality, mw.CheckHostel, HostelCheckIn) From a06f7cf97e2a734e089455361507526f3a6fce54 Mon Sep 17 00:00:00 2001 From: Ritesh Koushik Date: Wed, 7 Jan 2026 02:07:15 +0530 Subject: [PATCH 05/12] fix: Logging --- db/gen/gate-management-for-app.sql.go | 4 ++-- db/queries/gate-management-for-app.sql | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/db/gen/gate-management-for-app.sql.go b/db/gen/gate-management-for-app.sql.go index cc8cd484..e70fc7b1 100644 --- a/db/gen/gate-management-for-app.sql.go +++ b/db/gen/gate-management-for-app.sql.go @@ -85,12 +85,12 @@ const gateCheckStatusQuery = `-- name: GateCheckStatusQuery :one SELECT ad.payment_status AS accomodation_status, ( - SELECT MAX(logged_out) + SELECT MAX(logged_at) FROM gate_management WHERE student_id = s.id AND direction = 'IN' ) AS last_check_in, ( - SELECT MAX(logged_out) + SELECT MAX(logged_at) FROM gate_management WHERE student_id = s.id AND direction = 'OUT' diff --git a/db/queries/gate-management-for-app.sql b/db/queries/gate-management-for-app.sql index 07062bc6..7415dac3 100644 --- a/db/queries/gate-management-for-app.sql +++ b/db/queries/gate-management-for-app.sql @@ -94,12 +94,12 @@ FROM student_info si; SELECT ad.payment_status AS accomodation_status, ( - SELECT MAX(logged_out) + SELECT MAX(logged_at) FROM gate_management WHERE student_id = s.id AND direction = 'IN' ) AS last_check_in, ( - SELECT MAX(logged_out) + SELECT MAX(logged_at) FROM gate_management WHERE student_id = s.id AND direction = 'OUT' From 80ba4d45b2b4c1bf20ede34600fe7d00ca07de66 Mon Sep 17 00:00:00 2001 From: Ritesh Koushik Date: Wed, 7 Jan 2026 02:54:33 +0530 Subject: [PATCH 06/12] feat: Add name, college, email --- db/gen/gate-management-for-app.sql.go | 15 ++++++++++++++- db/queries/gate-management-for-app.sql | 3 +++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/db/gen/gate-management-for-app.sql.go b/db/gen/gate-management-for-app.sql.go index e70fc7b1..4a4b02e3 100644 --- a/db/gen/gate-management-for-app.sql.go +++ b/db/gen/gate-management-for-app.sql.go @@ -83,6 +83,9 @@ func (q *Queries) GateCheckInOutQuery(ctx context.Context, db DBTX, arg GateChec const gateCheckStatusQuery = `-- name: GateCheckStatusQuery :one SELECT + s.name, + s.college_name, + s.email, ad.payment_status AS accomodation_status, ( SELECT MAX(logged_at) @@ -102,6 +105,9 @@ WHERE ` type GateCheckStatusQueryRow struct { + Name string `json:"name"` + CollegeName string `json:"college_name"` + Email string `json:"email"` AccomodationStatus pgtype.Text `json:"accomodation_status"` LastCheckIn interface{} `json:"last_check_in"` LastCheckOut interface{} `json:"last_check_out"` @@ -110,7 +116,14 @@ type GateCheckStatusQueryRow struct { func (q *Queries) GateCheckStatusQuery(ctx context.Context, db DBTX, hospitalityID pgtype.Text) (GateCheckStatusQueryRow, error) { row := db.QueryRow(ctx, gateCheckStatusQuery, hospitalityID) var i GateCheckStatusQueryRow - err := row.Scan(&i.AccomodationStatus, &i.LastCheckIn, &i.LastCheckOut) + err := row.Scan( + &i.Name, + &i.CollegeName, + &i.Email, + &i.AccomodationStatus, + &i.LastCheckIn, + &i.LastCheckOut, + ) return i, err } diff --git a/db/queries/gate-management-for-app.sql b/db/queries/gate-management-for-app.sql index 7415dac3..7a7aaa81 100644 --- a/db/queries/gate-management-for-app.sql +++ b/db/queries/gate-management-for-app.sql @@ -92,6 +92,9 @@ FROM student_info si; -- name: GateCheckStatusQuery :one SELECT + s.name, + s.college_name, + s.email, ad.payment_status AS accomodation_status, ( SELECT MAX(logged_at) From 167294ec48dd962838ab10a4196632283f825dc7 Mon Sep 17 00:00:00 2001 From: Ritesh Koushik Date: Wed, 7 Jan 2026 03:31:41 +0530 Subject: [PATCH 07/12] feat: Add name, college_name and email to all status checks --- api/accomodation/gate.controllers.go | 72 ++++++++++++++++++---------- 1 file changed, 48 insertions(+), 24 deletions(-) diff --git a/api/accomodation/gate.controllers.go b/api/accomodation/gate.controllers.go index c2109fa8..fdb0ab41 100644 --- a/api/accomodation/gate.controllers.go +++ b/api/accomodation/gate.controllers.go @@ -381,18 +381,24 @@ func GateCheckInStatus(c *gin.Context) { }) if err == pgx.ErrNoRows { c.JSON(http.StatusNotFound, gin.H{ - "message": "Invalid hospitality ID", - "allow": false, - "reason": "Invalid hospitality ID provided.", + "message": "Invalid hospitality ID", + "allow": false, + "reason": "Invalid hospitality ID provided.", + "name": res.Name, + "email": res.Email, + "college_name": res.CollegeName, }) pkg.Log.WarnCtx(c, "[GATE-CHECKIN-WARN]: Invalid hospitality ID") return } if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ - "message": "Oops! Something happened. Please try again later", - "allow": false, - "reason": "Server error.", + "message": "Oops! Something happened. Please try again later", + "allow": false, + "reason": "Server error.", + "name": res.Name, + "email": res.Email, + "college_name": res.CollegeName, }) pkg.Log.ErrorCtx(c, "[GATE-CHECKIN-ERROR]: Failed to check gate status", err) return @@ -415,9 +421,12 @@ func GateCheckInStatus(c *gin.Context) { // Rule: Cannot check-in if already inside // Already inside is defined as last_check_in > last_check_out c.JSON(http.StatusOK, gin.H{ - "message": "Check-in status", - "allow": false, - "reason": "Already check-in.", + "message": "Check-in status", + "allow": false, + "reason": "Already check-in.", + "name": res.Name, + "email": res.Email, + "college_name": res.CollegeName, }) pkg.Log.InfoCtx(c, "[GATE-CHECKIN-STATUS]: Denied check-in for accomodation holder (already inside)") return @@ -431,9 +440,12 @@ func GateCheckInStatus(c *gin.Context) { if lastCheckInTime.Year() == now.Year() && lastCheckInTime.YearDay() == now.YearDay() { c.JSON(http.StatusOK, gin.H{ - "message": "Check-in status", - "allow": false, - "reason": "Already checked in today", + "message": "Check-in status", + "allow": false, + "reason": "Already checked in today", + "name": res.Name, + "email": res.Email, + "college_name": res.CollegeName, }) pkg.Log.InfoCtx(c, "[GATE-CHECKIN-STATUS]: Denied check-in for day scholar (already checked in today)") return @@ -442,9 +454,12 @@ func GateCheckInStatus(c *gin.Context) { } c.JSON(http.StatusOK, gin.H{ - "message": "Checkin status sent successfully", - "allow": true, - "reason": "", + "message": "Checkin status sent successfully", + "allow": true, + "reason": "", + "name": res.Name, + "email": res.Email, + "college_name": res.CollegeName, }) pkg.Log.SuccessCtx(c) } @@ -474,9 +489,12 @@ func GateCheckOutStatus(c *gin.Context) { }) if err == pgx.ErrNoRows { c.JSON(http.StatusNotFound, gin.H{ - "message": "Invalid hospitality ID", - "allow": false, - "reason": "Invalid hospitality ID provided", + "message": "Invalid hospitality ID", + "allow": false, + "reason": "Invalid hospitality ID provided", + "name": res.Name, + "email": res.Email, + "college_name": res.CollegeName, }) pkg.Log.WarnCtx(c, "[GATE-CHECKOUT-WARN]: Invalid hospitality ID") return @@ -501,18 +519,24 @@ func GateCheckOutStatus(c *gin.Context) { if !isCurrentlyInside { c.JSON(http.StatusOK, gin.H{ - "message": "Checkout status", - "allow": false, - "reason": "Not currently checkin in.", + "message": "Checkout status", + "allow": false, + "reason": "Not currently checked in.", + "name": res.Name, + "email": res.Email, + "college_name": res.CollegeName, }) pkg.Log.InfoCtx(c, "[GATE-CHECKOUT-STATUS]: Denied checkout (not inside)") return } c.JSON(http.StatusOK, gin.H{ - "message": "Checkout status sent successfully", - "allow": true, - "reason": "", + "message": "Checkout status sent successfully", + "allow": true, + "reason": "", + "name": res.Name, + "email": res.Email, + "college_name": res.CollegeName, }) pkg.Log.SuccessCtx(c) } From 2d6223bb61a0b6487513e4a5571b6d30402f0ab0 Mon Sep 17 00:00:00 2001 From: Ritesh Koushik Date: Wed, 7 Jan 2026 04:31:28 +0530 Subject: [PATCH 08/12] docs: Bruno --- bruno/hospitality-gate/checkIn.bru | 20 ++++++++++++++++++++ bruno/hospitality-gate/checkInStatus.bru | 20 ++++++++++++++++++++ bruno/hospitality-gate/checkOut.bru | 20 ++++++++++++++++++++ bruno/hospitality-gate/checkOutStatus.bru | 20 ++++++++++++++++++++ bruno/hospitality-gate/folder.bru | 8 ++++++++ 5 files changed, 88 insertions(+) create mode 100644 bruno/hospitality-gate/checkIn.bru create mode 100644 bruno/hospitality-gate/checkInStatus.bru create mode 100644 bruno/hospitality-gate/checkOut.bru create mode 100644 bruno/hospitality-gate/checkOutStatus.bru create mode 100644 bruno/hospitality-gate/folder.bru diff --git a/bruno/hospitality-gate/checkIn.bru b/bruno/hospitality-gate/checkIn.bru new file mode 100644 index 00000000..27ba81a2 --- /dev/null +++ b/bruno/hospitality-gate/checkIn.bru @@ -0,0 +1,20 @@ +meta { + name: checkIn + type: http + seq: 2 +} + +post { + url: {{baseUrl}}/accommodation/app/gate/check-in/:hospId + body: none + auth: inherit +} + +params:path { + hospId: +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/hospitality-gate/checkInStatus.bru b/bruno/hospitality-gate/checkInStatus.bru new file mode 100644 index 00000000..972160c4 --- /dev/null +++ b/bruno/hospitality-gate/checkInStatus.bru @@ -0,0 +1,20 @@ +meta { + name: checkInStatus + type: http + seq: 1 +} + +get { + url: {{baseUrl}}/accommodation/app/gate/status/check-in/:hospId + body: none + auth: inherit +} + +params:path { + hospId: +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/hospitality-gate/checkOut.bru b/bruno/hospitality-gate/checkOut.bru new file mode 100644 index 00000000..ab5d368f --- /dev/null +++ b/bruno/hospitality-gate/checkOut.bru @@ -0,0 +1,20 @@ +meta { + name: checkOut + type: http + seq: 4 +} + +post { + url: {{baseUrl}}/accommodation/app/gate/check-out/:hospId + body: none + auth: inherit +} + +params:path { + hospId: +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/hospitality-gate/checkOutStatus.bru b/bruno/hospitality-gate/checkOutStatus.bru new file mode 100644 index 00000000..2b475b84 --- /dev/null +++ b/bruno/hospitality-gate/checkOutStatus.bru @@ -0,0 +1,20 @@ +meta { + name: checkOutStatus + type: http + seq: 3 +} + +get { + url: {{baseUrl}}/accommodation/app/gate/status/check-out/:hospId + body: none + auth: inherit +} + +params:path { + hospId: +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/hospitality-gate/folder.bru b/bruno/hospitality-gate/folder.bru new file mode 100644 index 00000000..5e337e29 --- /dev/null +++ b/bruno/hospitality-gate/folder.bru @@ -0,0 +1,8 @@ +meta { + name: hospitality-gate + seq: 18 +} + +auth { + mode: inherit +} From ba531c52aa78281e6490c148cd01a367430a6649 Mon Sep 17 00:00:00 2001 From: Ritesh Koushik Date: Wed, 7 Jan 2026 04:31:35 +0530 Subject: [PATCH 09/12] fix: MAX sqlc query --- db/gen/gate-management-for-app.sql.go | 27 ++++++++++++++++---------- db/queries/gate-management-for-app.sql | 15 ++++++++++---- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/db/gen/gate-management-for-app.sql.go b/db/gen/gate-management-for-app.sql.go index 4a4b02e3..1cd515ef 100644 --- a/db/gen/gate-management-for-app.sql.go +++ b/db/gen/gate-management-for-app.sql.go @@ -88,15 +88,22 @@ SELECT s.email, ad.payment_status AS accomodation_status, ( - SELECT MAX(logged_at) + SELECT logged_at FROM gate_management - WHERE student_id = s.id AND direction = 'IN' + WHERE + student_id = s.id + AND direction = 'IN' + ORDER BY logged_at DESC + LIMIT 1 ) AS last_check_in, ( - SELECT MAX(logged_at) + SELECT logged_at FROM gate_management WHERE - student_id = s.id AND direction = 'OUT' + student_id = s.id + AND direction = 'OUT' + ORDER BY logged_at DESC + LIMIT 1 ) AS last_check_out FROM student s LEFT JOIN accomodation_details ad ON s.id = ad.student_id @@ -105,12 +112,12 @@ WHERE ` type GateCheckStatusQueryRow struct { - Name string `json:"name"` - CollegeName string `json:"college_name"` - Email string `json:"email"` - AccomodationStatus pgtype.Text `json:"accomodation_status"` - LastCheckIn interface{} `json:"last_check_in"` - LastCheckOut interface{} `json:"last_check_out"` + Name string `json:"name"` + CollegeName string `json:"college_name"` + Email string `json:"email"` + AccomodationStatus pgtype.Text `json:"accomodation_status"` + LastCheckIn pgtype.Timestamp `json:"last_check_in"` + LastCheckOut pgtype.Timestamp `json:"last_check_out"` } func (q *Queries) GateCheckStatusQuery(ctx context.Context, db DBTX, hospitalityID pgtype.Text) (GateCheckStatusQueryRow, error) { diff --git a/db/queries/gate-management-for-app.sql b/db/queries/gate-management-for-app.sql index 7a7aaa81..05f11f2b 100644 --- a/db/queries/gate-management-for-app.sql +++ b/db/queries/gate-management-for-app.sql @@ -97,15 +97,22 @@ SELECT s.email, ad.payment_status AS accomodation_status, ( - SELECT MAX(logged_at) + SELECT logged_at FROM gate_management - WHERE student_id = s.id AND direction = 'IN' + WHERE + student_id = s.id + AND direction = 'IN' + ORDER BY logged_at DESC + LIMIT 1 ) AS last_check_in, ( - SELECT MAX(logged_at) + SELECT logged_at FROM gate_management WHERE - student_id = s.id AND direction = 'OUT' + student_id = s.id + AND direction = 'OUT' + ORDER BY logged_at DESC + LIMIT 1 ) AS last_check_out FROM student s LEFT JOIN accomodation_details ad ON s.id = ad.student_id From 42212e5d499aa12419dde951dde342a2b971ce73 Mon Sep 17 00:00:00 2001 From: Ritesh Koushik Date: Wed, 7 Jan 2026 04:31:55 +0530 Subject: [PATCH 10/12] fix: Logout --- middleware/rbac.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/middleware/rbac.go b/middleware/rbac.go index 01dfc826..286f6528 100644 --- a/middleware/rbac.go +++ b/middleware/rbac.go @@ -61,7 +61,7 @@ func CheckHospitality(c *gin.Context) { c.AbortWithStatusJSON(http.StatusForbidden, gin.H{ "message": "Hospitality access denied.", }) - pkg.Log.WarnCtx(c, "[RBAC-FAIL]: Does not have hospitality-panel role") + pkg.Log.WarnCtx(c, "[RBAC-FAIL]: Does not have hospitality role") } func CheckGate(c *gin.Context) { From 65e5ca2f6d8e176566c58849503227de0e01ec96 Mon Sep 17 00:00:00 2001 From: Ritesh Koushik Date: Wed, 7 Jan 2026 04:32:35 +0530 Subject: [PATCH 11/12] fix: Remove assertions in gate --- api/accomodation/gate.controllers.go | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/api/accomodation/gate.controllers.go b/api/accomodation/gate.controllers.go index fdb0ab41..2b896979 100644 --- a/api/accomodation/gate.controllers.go +++ b/api/accomodation/gate.controllers.go @@ -404,18 +404,13 @@ func GateCheckInStatus(c *gin.Context) { return } - // Type Assertions to handle interface issues - lastCheckIn, lastCheckInOK := res.LastCheckIn.(pgtype.Timestamp) - lastCheckOut, lastCheckOutOK := res.LastCheckOut.(pgtype.Timestamp) - // hasAccomodation would be true if payment_status is not NULL or empty hasAccomodation := res.AccomodationStatus.Valid && res.AccomodationStatus.String != "" if hasAccomodation { // Rule: Cannot check-in if already inside - isValidCheckIn := lastCheckInOK && lastCheckIn.Valid - isValidCheckOut := (!lastCheckOutOK || !lastCheckOut.Valid || lastCheckIn.Time.After(lastCheckOut.Time)) - isAlreadyInside := isValidCheckIn && isValidCheckOut + // A person is inside if they have a valid check-in and either no valid checkout or check-in is after checkout. + isAlreadyInside := res.LastCheckIn.Valid && (!res.LastCheckOut.Valid || res.LastCheckIn.Time.After(res.LastCheckOut.Time)) if isAlreadyInside { // Rule: Cannot check-in if already inside @@ -434,9 +429,9 @@ func GateCheckInStatus(c *gin.Context) { // If not inside then fall through to success case } else { // does not have accomodation // Rule: Day scholars can check-in once per day - if lastCheckIn.Valid { + if res.LastCheckIn.Valid { now := time.Now() - lastCheckInTime := lastCheckIn.Time + lastCheckInTime := res.LastCheckIn.Time if lastCheckInTime.Year() == now.Year() && lastCheckInTime.YearDay() == now.YearDay() { c.JSON(http.StatusOK, gin.H{ @@ -507,15 +502,9 @@ func GateCheckOutStatus(c *gin.Context) { return } - // Type assertions - lastCheckIn, lastCheckInOK := res.LastCheckIn.(pgtype.Timestamp) - lastCheckOut, lastCheckOutOK := res.LastCheckIn.(pgtype.Timestamp) - // A user can checkout only if they are currently checked in. A user is // inside if their last checkin is more recent that their last checkout - isValidCheckIn := lastCheckInOK && lastCheckIn.Valid - isValidCheckOut := (!lastCheckOutOK || !lastCheckOut.Valid || !lastCheckIn.Time.After(lastCheckOut.Time)) - isCurrentlyInside := isValidCheckIn && isValidCheckOut + isCurrentlyInside := res.LastCheckIn.Valid && (!res.LastCheckOut.Valid || res.LastCheckIn.Time.After(res.LastCheckOut.Time)) if !isCurrentlyInside { c.JSON(http.StatusOK, gin.H{ From 5a0aeede01298f64ee4cfe064bc43339a182228d Mon Sep 17 00:00:00 2001 From: Ritesh Koushik Date: Wed, 7 Jan 2026 04:43:59 +0530 Subject: [PATCH 12/12] fix: hasLeftAlready case in accommodation --- api/accomodation/gate.controllers.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/api/accomodation/gate.controllers.go b/api/accomodation/gate.controllers.go index 2b896979..a60f38f0 100644 --- a/api/accomodation/gate.controllers.go +++ b/api/accomodation/gate.controllers.go @@ -410,7 +410,7 @@ func GateCheckInStatus(c *gin.Context) { if hasAccomodation { // Rule: Cannot check-in if already inside // A person is inside if they have a valid check-in and either no valid checkout or check-in is after checkout. - isAlreadyInside := res.LastCheckIn.Valid && (!res.LastCheckOut.Valid || res.LastCheckIn.Time.After(res.LastCheckOut.Time)) + isAlreadyInside := res.LastCheckIn.Valid && !res.LastCheckOut.Valid if isAlreadyInside { // Rule: Cannot check-in if already inside @@ -426,6 +426,22 @@ func GateCheckInStatus(c *gin.Context) { pkg.Log.InfoCtx(c, "[GATE-CHECKIN-STATUS]: Denied check-in for accomodation holder (already inside)") return } + + hasAlreadyLeft := res.LastCheckIn.Valid && res.LastCheckOut.Valid + + if hasAlreadyLeft { + // Rule: Cannot check-in once left + c.JSON(http.StatusOK, gin.H{ + "message": "Check-in status", + "allow": false, + "reason": "Checked out once already.", + "name": res.Name, + "email": res.Email, + "college_name": res.CollegeName, + }) + pkg.Log.InfoCtx(c, "[GATE-CHECKIN-STATUS]: Denied check-in for accomodation holder (already inside)") + return + } // If not inside then fall through to success case } else { // does not have accomodation // Rule: Day scholars can check-in once per day