Skip to content
189 changes: 189 additions & 0 deletions api/accomodation/gate.controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,3 +356,192 @@ 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.",
"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.",
"name": res.Name,
"email": res.Email,
"college_name": res.CollegeName,
})
pkg.Log.ErrorCtx(c, "[GATE-CHECKIN-ERROR]: Failed to check gate status", err)
return
}

// 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
// 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

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.",
"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
}

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
if res.LastCheckIn.Valid {
now := time.Now()
lastCheckInTime := res.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",
"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
}
}
}

c.JSON(http.StatusOK, gin.H{
"message": "Checkin status sent successfully",
"allow": true,
"reason": "",
"name": res.Name,
"email": res.Email,
"college_name": res.CollegeName,
})
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",
"name": res.Name,
"email": res.Email,
"college_name": res.CollegeName,
})
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
}

// 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
isCurrentlyInside := res.LastCheckIn.Valid && (!res.LastCheckOut.Valid || res.LastCheckIn.Time.After(res.LastCheckOut.Time))

if !isCurrentlyInside {
c.JSON(http.StatusOK, gin.H{
"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": "",
"name": res.Name,
"email": res.Email,
"college_name": res.CollegeName,
})
pkg.Log.SuccessCtx(c)
}
3 changes: 2 additions & 1 deletion api/accomodation/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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/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, GateStatus)
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)
Expand Down
20 changes: 20 additions & 0 deletions bruno/hospitality-gate/checkIn.bru
Original file line number Diff line number Diff line change
@@ -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
}
20 changes: 20 additions & 0 deletions bruno/hospitality-gate/checkInStatus.bru
Original file line number Diff line number Diff line change
@@ -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
}
20 changes: 20 additions & 0 deletions bruno/hospitality-gate/checkOut.bru
Original file line number Diff line number Diff line change
@@ -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
}
20 changes: 20 additions & 0 deletions bruno/hospitality-gate/checkOutStatus.bru
Original file line number Diff line number Diff line change
@@ -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
}
8 changes: 8 additions & 0 deletions bruno/hospitality-gate/folder.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
meta {
name: hospitality-gate
seq: 18
}

auth {
mode: inherit
}
53 changes: 53 additions & 0 deletions db/gen/gate-management-for-app.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 29 additions & 0 deletions db/queries/gate-management-for-app.sql
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,32 @@ 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
s.name,
s.college_name,
s.email,
ad.payment_status AS accomodation_status,
(
SELECT logged_at
FROM gate_management
WHERE
student_id = s.id
AND direction = 'IN'
ORDER BY logged_at DESC
LIMIT 1
) AS last_check_in,
(
SELECT logged_at
FROM gate_management
WHERE
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
WHERE
s.hospitality_id = $1;
2 changes: 1 addition & 1 deletion middleware/rbac.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down