diff --git a/app/src/pages/profile/Profile.tsx b/app/src/pages/profile/Profile.tsx
index 121b2a0..cb74a88 100644
--- a/app/src/pages/profile/Profile.tsx
+++ b/app/src/pages/profile/Profile.tsx
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
import { useNavigate, Link } from 'react-router-dom';
import {
ShieldCheck, LogOut, PlusCircle, AlertCircle, Pencil, UserCheck,
- Inbox, ServerCrash,
+ Inbox, ServerCrash, Info,
} from 'lucide-react';
import { MainLayout } from '../../components/layout/MainLayout';
import { ComplaintCard } from '../../components/ComplaintCard';
@@ -254,6 +254,15 @@ export function Profile() {
Your Complaints
+
+
+
+
+ Posts can only be edited within 30 minutes of creation
+
+
+
+
{!postsLoading && (
{posts.length}
)}
diff --git a/handlers/centrehead_post.go b/handlers/centrehead_post.go
index 9e31e12..dba8fc3 100644
--- a/handlers/centrehead_post.go
+++ b/handlers/centrehead_post.go
@@ -127,6 +127,12 @@ func (h *PostHandler) CentreheadPostEdit(c *gin.Context) {
c.JSON(403, gin.H{"error": "you are not authorized for this action"})
return
}
+
+ // limit the edit window only for 30 minutes
+ if time.Since(post.CreatedAt) >= 30*time.Minute {
+ c.JSON(403, gin.H{"error": "edit window has been expired"})
+ return
+ }
var inputs CentreheadPostEditType
inputs.UpdatedAt = time.Now()
@@ -173,6 +179,12 @@ func (h *PostHandler) CentreheadPostDelete(c *gin.Context) {
return
}
+ // restrict post deletion after 30 minutes
+ if time.Since(post.CreatedAt) >= 30*time.Minute {
+ c.JSON(403, gin.H{"error": "deletion window has been expired"})
+ return
+ }
+
if result := h.DB.Delete(&post); result.Error != nil {
c.JSON(500, gin.H{"error": "failed deleting the post"})
return
diff --git a/handlers/faculty_post.go b/handlers/faculty_post.go
index 8883a1d..2eb29cf 100644
--- a/handlers/faculty_post.go
+++ b/handlers/faculty_post.go
@@ -136,6 +136,12 @@ func (h *PostHandler) FacultyPostEdit(c *gin.Context) {
return
}
+ // limit the edit window only for 30 minutes
+ if time.Since(post.CreatedAt) >= 30*time.Minute {
+ c.JSON(403, gin.H{"error": "edit window has been expired"})
+ return
+ }
+
var inputs FacultyPostEditType
inputs.UpdatedAt = time.Now()
if err := c.ShouldBindJSON(&inputs); err != nil {
@@ -175,11 +181,18 @@ func (h *PostHandler) FacultyPostDelete(c *gin.Context) {
return
}
+ // check if the author is trying to delete
if post.FacultyID != userID.(uint) {
c.JSON(403, gin.H{"error": "you are not authorized for this action"})
return
}
+ // restrict post deletion after 30 minutes
+ if time.Since(post.CreatedAt) >= 30*time.Minute {
+ c.JSON(403, gin.H{"error": "deletion window has been expired"})
+ return
+ }
+
if result := h.DB.Delete(&post); result.Error != nil {
c.JSON(500, gin.H{"error": "failed deleting the post"})
return
diff --git a/handlers/warden_post.go b/handlers/warden_post.go
index f7289a4..51fede3 100644
--- a/handlers/warden_post.go
+++ b/handlers/warden_post.go
@@ -132,6 +132,12 @@ func (h *PostHandler) WardenPostEdit(c *gin.Context) {
return
}
+ // limit the edit window only for 30 minutes
+ if time.Since(post.CreatedAt) >= 30*time.Minute {
+ c.JSON(403, gin.H{"error": "edit window has been expired"})
+ return
+ }
+
var inputs WardenPostEditType
inputs.UpdatedAt = time.Now()
if err := c.ShouldBindJSON(&inputs); err != nil {
@@ -177,6 +183,12 @@ func (h *PostHandler) WardenPostDelete(c *gin.Context) {
return
}
+ // restrict post deletion after 30 minutes
+ if time.Since(post.CreatedAt) >= 30*time.Minute {
+ c.JSON(403, gin.H{"error": "deletion window has been expired"})
+ return
+ }
+
if result := h.DB.Delete(&post); result.Error != nil {
c.JSON(500, gin.H{"error": "failed deleting the post"})
return
diff --git a/routes/post.go b/routes/post.go
index a619df8..ea60f4c 100644
--- a/routes/post.go
+++ b/routes/post.go
@@ -8,18 +8,22 @@ import (
)
func PostRoute(e *gin.Engine, h *handlers.PostHandler) {
+ // APIs for new post
e.POST("/api/post/faculty", middleware.IsAuthenticated(), h.FacultyPost)
e.POST("/api/post/warden", middleware.IsAuthenticated(), h.WardenPost)
e.POST("/api/post/centrehead", middleware.IsAuthenticated(), h.CentreheadPost)
+ // APIs for updating the post
e.PATCH("/api/post/faculty/edit/:post_id", middleware.IsAuthenticated(), h.FacultyPostEdit)
e.PATCH("/api/post/warden/edit/:post_id", middleware.IsAuthenticated(), h.WardenPostEdit)
e.PATCH("/api/post/centrehead/edit/:post_id", middleware.IsAuthenticated(), h.CentreheadPostEdit)
+ // APIs for deleting the post
e.DELETE("/api/post/faculty/delete/:post_id", middleware.IsAuthenticated(), h.FacultyPostDelete)
e.DELETE("/api/post/warden/delete/:post_id", middleware.IsAuthenticated(), h.WardenPostDelete)
e.DELETE("/api/post/centrehead/delete/:post_id", middleware.IsAuthenticated(), h.CentreheadPostDelete)
+ // APIs for getting the posts
e.GET("/api/post/faculty", middleware.IsAuthenticated(), h.GetFacultyPosts)
e.GET("/api/post/warden", middleware.IsAuthenticated(), h.GetWardenPosts)
e.GET("/api/post/centrehead", middleware.IsAuthenticated(), h.GetCentreheadPosts)
diff --git a/test/centrehead_post_test.go b/test/centrehead_post_test.go
index 259ac05..a508ffa 100644
--- a/test/centrehead_post_test.go
+++ b/test/centrehead_post_test.go
@@ -3,6 +3,7 @@ package test
import (
"net/http"
"testing"
+ "time"
"github.com/ayush00git/cms-web/models"
)
@@ -104,6 +105,18 @@ func TestCentreheadPostEdit_Unauthenticated(t *testing.T) {
assertStatus(t, rec, 401)
}
+func TestCentreheadPostEdit_ExpiredWindow(t *testing.T) {
+ db := newTestDB(t)
+ ch := seedCentrehead(t, db, "ch.expired@iit.ac.in")
+ post := models.CentreheadPost{CentreheadID: ch.ID, TypeOfPost: models.TypeCivil, Title: "old", Description: "old"}
+ db.Create(&post)
+ db.Model(&post).Update("created_at", time.Now().Add(-31*time.Minute))
+
+ e := newPostRouter(db, authAs(ch.ID, ch.Email))
+ rec := doRequest(t, e, http.MethodPatch, "/api/post/centrehead/edit/1", map[string]any{"title": "new"})
+ assertStatus(t, rec, 403)
+}
+
// --- CentreheadPostDelete ---------------------------------------------------
func TestCentreheadPostDelete_Success(t *testing.T) {
@@ -151,6 +164,18 @@ func TestCentreheadPostDelete_Unauthenticated(t *testing.T) {
assertStatus(t, rec, 401)
}
+func TestCentreheadPostDelete_ExpiredWindow(t *testing.T) {
+ db := newTestDB(t)
+ ch := seedCentrehead(t, db, "ch.delexpired@iit.ac.in")
+ post := models.CentreheadPost{CentreheadID: ch.ID, TypeOfPost: models.TypeCivil, Title: "t", Description: "d"}
+ db.Create(&post)
+ db.Model(&post).Update("created_at", time.Now().Add(-31*time.Minute))
+
+ e := newPostRouter(db, authAs(ch.ID, ch.Email))
+ rec := doRequest(t, e, http.MethodDelete, "/api/post/centrehead/delete/1", nil)
+ assertStatus(t, rec, 403)
+}
+
// --- GetCentreheadPosts -----------------------------------------------------
func TestGetCentreheadPosts_Success(t *testing.T) {
diff --git a/test/faculty_post_test.go b/test/faculty_post_test.go
index b04032e..f1fa323 100644
--- a/test/faculty_post_test.go
+++ b/test/faculty_post_test.go
@@ -3,6 +3,7 @@ package test
import (
"net/http"
"testing"
+ "time"
"github.com/ayush00git/cms-web/models"
)
@@ -116,6 +117,18 @@ func TestFacultyPostEdit_Unauthenticated(t *testing.T) {
assertStatus(t, rec, 401)
}
+func TestFacultyPostEdit_ExpiredWindow(t *testing.T) {
+ db := newTestDB(t)
+ f := seedFaculty(t, db, "fac.expired@iit.ac.in")
+ post := models.FacultyPost{FacultyID: f.ID, Place: models.PlaceDepartmental, TypeOfPost: models.TypeCivil, Title: "old", Description: "old desc"}
+ db.Create(&post)
+ db.Model(&post).Update("created_at", time.Now().Add(-31*time.Minute))
+
+ e := newPostRouter(db, authAs(f.ID, f.Email))
+ rec := doRequest(t, e, http.MethodPatch, "/api/post/faculty/edit/1", map[string]any{"title": "new"})
+ assertStatus(t, rec, 403)
+}
+
// --- FacultyPostDelete ------------------------------------------------------
func TestFacultyPostDelete_Success(t *testing.T) {
@@ -164,6 +177,18 @@ func TestFacultyPostDelete_Unauthenticated(t *testing.T) {
assertStatus(t, rec, 401)
}
+func TestFacultyPostDelete_ExpiredWindow(t *testing.T) {
+ db := newTestDB(t)
+ f := seedFaculty(t, db, "fac.delexpired@iit.ac.in")
+ post := models.FacultyPost{FacultyID: f.ID, Place: models.PlaceDepartmental, TypeOfPost: models.TypeCivil, Title: "t", Description: "d"}
+ db.Create(&post)
+ db.Model(&post).Update("created_at", time.Now().Add(-31*time.Minute))
+
+ e := newPostRouter(db, authAs(f.ID, f.Email))
+ rec := doRequest(t, e, http.MethodDelete, "/api/post/faculty/delete/1", nil)
+ assertStatus(t, rec, 403)
+}
+
// --- GetFacultyPosts --------------------------------------------------------
func TestGetFacultyPosts_Success(t *testing.T) {
diff --git a/test/warden_post_test.go b/test/warden_post_test.go
index 1a7faee..72af71c 100644
--- a/test/warden_post_test.go
+++ b/test/warden_post_test.go
@@ -3,6 +3,7 @@ package test
import (
"net/http"
"testing"
+ "time"
"github.com/ayush00git/cms-web/models"
)
@@ -105,6 +106,18 @@ func TestWardenPostEdit_Unauthenticated(t *testing.T) {
assertStatus(t, rec, 401)
}
+func TestWardenPostEdit_ExpiredWindow(t *testing.T) {
+ db := newTestDB(t)
+ w := seedWarden(t, db, "war.expired@iit.ac.in")
+ post := models.WardenPost{WardenID: w.ID, RoomNumber: "A-1", TypeOfPost: models.TypeCivil, Title: "old", Description: "old"}
+ db.Create(&post)
+ db.Model(&post).Update("created_at", time.Now().Add(-31*time.Minute))
+
+ e := newPostRouter(db, authAs(w.ID, w.Email))
+ rec := doRequest(t, e, http.MethodPatch, "/api/post/warden/edit/1", map[string]any{"title": "new"})
+ assertStatus(t, rec, 403)
+}
+
// --- WardenPostDelete -------------------------------------------------------
func TestWardenPostDelete_Success(t *testing.T) {
@@ -152,6 +165,18 @@ func TestWardenPostDelete_Unauthenticated(t *testing.T) {
assertStatus(t, rec, 401)
}
+func TestWardenPostDelete_ExpiredWindow(t *testing.T) {
+ db := newTestDB(t)
+ w := seedWarden(t, db, "war.delexpired@iit.ac.in")
+ post := models.WardenPost{WardenID: w.ID, RoomNumber: "A-1", TypeOfPost: models.TypeCivil, Title: "t", Description: "d"}
+ db.Create(&post)
+ db.Model(&post).Update("created_at", time.Now().Add(-31*time.Minute))
+
+ e := newPostRouter(db, authAs(w.ID, w.Email))
+ rec := doRequest(t, e, http.MethodDelete, "/api/post/warden/delete/1", nil)
+ assertStatus(t, rec, 403)
+}
+
// --- GetWardenPosts ---------------------------------------------------------
func TestGetWardenPosts_Success(t *testing.T) {