From 5f1f647b84a79eef90ebf35906dbe538a92eb504 Mon Sep 17 00:00:00 2001 From: harsh15044 <75984501+harsh15044@users.noreply.github.com> Date: Thu, 4 Sep 2025 11:31:34 +0530 Subject: [PATCH 1/3] added endpoint to fetch combined list of lost and found items sorted by creation time --- backend-go/internal/controller/lost.go | 117 +++++++++++++++++++++++++ backend-go/internal/router/routes.go | 6 ++ 2 files changed, 123 insertions(+) diff --git a/backend-go/internal/controller/lost.go b/backend-go/internal/controller/lost.go index 542625c8..fe5a99d0 100644 --- a/backend-go/internal/controller/lost.go +++ b/backend-go/internal/controller/lost.go @@ -5,7 +5,9 @@ import ( "fmt" "net/http" "os" + "sort" "strconv" + "time" "github.com/LambdaIITH/Dashboard/backend/config" lost "github.com/LambdaIITH/Dashboard/backend/internal/db" @@ -133,6 +135,121 @@ func GetAllItemsHandler(c *gin.Context) { c.JSON(http.StatusOK, response) } +/* +GetCombinedAllItemsHandler fetches all the lost and found items and returns them as a JSON response ordered by creation time. +*/ +func GetCombinedAllItemsHandler(c *gin.Context) { + + maxLimit := 100 + if limitStr := c.Query("max_limit"); limitStr != "" { + if limit, err := strconv.Atoi(limitStr); err == nil { + maxLimit = limit + } + } + + //Step 1: Fetching lost items AND found items + lostItems, err := lost.GetAllLostItems(c) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch items"}) + fmt.Println(err) + return + } + if len(lostItems) > maxLimit { + lostItems = lostItems[:maxLimit] + } + + foundItems, err := lost.GetAllFoundItems(c) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch items"}) + return + } + + if len(foundItems) > maxLimit { + foundItems = foundItems[:maxLimit] + } + + //Step 2: Fetching images for all lost items AND found items + lostRows, err := config.DB.Query(c, "SELECT item_id, image_url FROM lost_images") + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch images"}) + return + } + defer lostRows.Close() + + foundRows, err := config.DB.Query(c, "SELECT item_id, image_url FROM found_images") + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch images"}) + return + } + defer foundRows.Close() + + //Step 3: Organize the image urls by item ID + imageDict := make(map[int][]string) + for lostRows.Next() { + var img schema.ImageURI + if err := lostRows.Scan(&img.ItemID, &img.ImageURL); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to scan images"}) + return + } + imageDict[img.ItemID] = append(imageDict[img.ItemID], img.ImageURL) + } + + for foundRows.Next() { + var img schema.ImageURI + if err := foundRows.Scan(&img.ItemID, &img.ImageURL); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to scan images"}) + return + } + imageDict[img.ItemID] = append(imageDict[img.ItemID], img.ImageURL) + } + + response := make([]map[string]any, 0, len(lostItems)+len(foundItems)) + + for _, item := range lostItems { + images := imageDict[item.ID] + if images == nil { + images = []string{} + } + + itemData := map[string]any{ + "id": item.ID, + "name": item.ItemName, + "images": images, + "created_at": item.CreatedAt.Format(time.RFC3339), + "type": "lost", + } + response = append(response, itemData) + } + for _, item := range foundItems { + images := imageDict[item.ID] + if images == nil { + images = []string{} + } + + itemData := map[string]any{ + "id": item.ID, + "name": item.ItemName, + "images": images, + "created_at": item.CreatedAt.Format(time.RFC3339), + "type": "found", + } + response = append(response, itemData) + } + + sort.Slice(response, func(i, j int) bool { + t1, _ := time.Parse(time.RFC3339, response[i]["created_at"].(string)) + t2, _ := time.Parse(time.RFC3339, response[j]["created_at"].(string)) + return t1.After(t2) //newest first + }) + + if len(response) > maxLimit { + response = response[:maxLimit] + } + + // Step 4: Return the response + c.JSON(http.StatusOK, response) +} + /* GetItemByIdHandler fetches a particular lost item by its ID and returns it as a JSON response. */ diff --git a/backend-go/internal/router/routes.go b/backend-go/internal/router/routes.go index a15335c2..6555f57c 100644 --- a/backend-go/internal/router/routes.go +++ b/backend-go/internal/router/routes.go @@ -77,6 +77,12 @@ func SetupRoutes(router *gin.Engine) { foundGroup.GET("/search", controller.SearchFoundItemHandler) } + //Group routes for lost-found items + lostFoundGroup := router.Group("/lost_found") + { + lostFoundGroup.GET("/all", controller.GetCombinedAllItemsHandler) //optional parameter max_limit + } + //Group routes for timetable/calendar timetableGroup := router.Group("/schedule") { From f05505ba8c50afe1d247545e2a5159029b678032 Mon Sep 17 00:00:00 2001 From: harsh15044 <75984501+harsh15044@users.noreply.github.com> Date: Tue, 27 Jan 2026 23:32:45 +0530 Subject: [PATCH 2/3] fix: improve error handling and remove item limit checks in GetCombinedAllItemsHandler --- backend-go/internal/controller/lost.go | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/backend-go/internal/controller/lost.go b/backend-go/internal/controller/lost.go index fe5a99d0..f188a837 100644 --- a/backend-go/internal/controller/lost.go +++ b/backend-go/internal/controller/lost.go @@ -154,9 +154,7 @@ func GetCombinedAllItemsHandler(c *gin.Context) { fmt.Println(err) return } - if len(lostItems) > maxLimit { - lostItems = lostItems[:maxLimit] - } + foundItems, err := lost.GetAllFoundItems(c) if err != nil { @@ -164,9 +162,7 @@ func GetCombinedAllItemsHandler(c *gin.Context) { return } - if len(foundItems) > maxLimit { - foundItems = foundItems[:maxLimit] - } + //Step 2: Fetching images for all lost items AND found items lostRows, err := config.DB.Query(c, "SELECT item_id, image_url FROM lost_images") @@ -237,9 +233,21 @@ func GetCombinedAllItemsHandler(c *gin.Context) { } sort.Slice(response, func(i, j int) bool { - t1, _ := time.Parse(time.RFC3339, response[i]["created_at"].(string)) - t2, _ := time.Parse(time.RFC3339, response[j]["created_at"].(string)) - return t1.After(t2) //newest first + t1, err1 := time.Parse(time.RFC3339, response[i]["created_at"].(string)) + t2, err2 := time.Parse(time.RFC3339, response[j]["created_at"].(string)) + if err1 != nil && err2 != nil { + // Both times invalid, keep original order + return false + } + if err1 != nil { + // i is invalid, j is valid: j comes first (i is "older") + return false + } + if err2 != nil { + // j is invalid, i is valid: i comes first (i is "newer") + return true + } + return t1.After(t2) // newest first }) if len(response) > maxLimit { From d3ff8d7d4e32990fe4425087672cdfcf67fe9f64 Mon Sep 17 00:00:00 2001 From: harsh15044 <75984501+harsh15044@users.noreply.github.com> Date: Wed, 28 Jan 2026 00:03:48 +0530 Subject: [PATCH 3/3] refactor: removed manual image fetching and simplified time field usage --- backend-go/internal/controller/lost.go | 62 ++++---------------------- backend-go/internal/router/routes.go | 2 +- 2 files changed, 9 insertions(+), 55 deletions(-) diff --git a/backend-go/internal/controller/lost.go b/backend-go/internal/controller/lost.go index f188a837..fead37d9 100644 --- a/backend-go/internal/controller/lost.go +++ b/backend-go/internal/controller/lost.go @@ -151,7 +151,7 @@ func GetCombinedAllItemsHandler(c *gin.Context) { lostItems, err := lost.GetAllLostItems(c) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch items"}) - fmt.Println(err) + return } @@ -164,45 +164,11 @@ func GetCombinedAllItemsHandler(c *gin.Context) { - //Step 2: Fetching images for all lost items AND found items - lostRows, err := config.DB.Query(c, "SELECT item_id, image_url FROM lost_images") - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch images"}) - return - } - defer lostRows.Close() - - foundRows, err := config.DB.Query(c, "SELECT item_id, image_url FROM found_images") - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch images"}) - return - } - defer foundRows.Close() - - //Step 3: Organize the image urls by item ID - imageDict := make(map[int][]string) - for lostRows.Next() { - var img schema.ImageURI - if err := lostRows.Scan(&img.ItemID, &img.ImageURL); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to scan images"}) - return - } - imageDict[img.ItemID] = append(imageDict[img.ItemID], img.ImageURL) - } - - for foundRows.Next() { - var img schema.ImageURI - if err := foundRows.Scan(&img.ItemID, &img.ImageURL); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to scan images"}) - return - } - imageDict[img.ItemID] = append(imageDict[img.ItemID], img.ImageURL) - } - + //Step 2: Initialize response slice response := make([]map[string]any, 0, len(lostItems)+len(foundItems)) for _, item := range lostItems { - images := imageDict[item.ID] + images := item.Images if images == nil { images = []string{} } @@ -211,13 +177,13 @@ func GetCombinedAllItemsHandler(c *gin.Context) { "id": item.ID, "name": item.ItemName, "images": images, - "created_at": item.CreatedAt.Format(time.RFC3339), + "created_at": item.CreatedAt, "type": "lost", } response = append(response, itemData) } for _, item := range foundItems { - images := imageDict[item.ID] + images := item.Images if images == nil { images = []string{} } @@ -226,27 +192,15 @@ func GetCombinedAllItemsHandler(c *gin.Context) { "id": item.ID, "name": item.ItemName, "images": images, - "created_at": item.CreatedAt.Format(time.RFC3339), + "created_at": item.CreatedAt, "type": "found", } response = append(response, itemData) } sort.Slice(response, func(i, j int) bool { - t1, err1 := time.Parse(time.RFC3339, response[i]["created_at"].(string)) - t2, err2 := time.Parse(time.RFC3339, response[j]["created_at"].(string)) - if err1 != nil && err2 != nil { - // Both times invalid, keep original order - return false - } - if err1 != nil { - // i is invalid, j is valid: j comes first (i is "older") - return false - } - if err2 != nil { - // j is invalid, i is valid: i comes first (i is "newer") - return true - } + t1 := response[i]["created_at"].(time.Time) + t2 := response[j]["created_at"].(time.Time) return t1.After(t2) // newest first }) diff --git a/backend-go/internal/router/routes.go b/backend-go/internal/router/routes.go index 6555f57c..7d6dedc7 100644 --- a/backend-go/internal/router/routes.go +++ b/backend-go/internal/router/routes.go @@ -80,7 +80,7 @@ func SetupRoutes(router *gin.Engine) { //Group routes for lost-found items lostFoundGroup := router.Group("/lost_found") { - lostFoundGroup.GET("/all", controller.GetCombinedAllItemsHandler) //optional parameter max_limit + lostFoundGroup.GET("/", controller.GetCombinedAllItemsHandler) //optional parameter max_limit } //Group routes for timetable/calendar