From e7711769e21199b369a7a41fc1a5ce6d4d4ec6ee Mon Sep 17 00:00:00 2001 From: Sandy Date: Sat, 14 Feb 2026 17:06:02 +0530 Subject: [PATCH 01/10] Add files via upload I have zero idea whether this works or not but iv'e tried to make it as accurate to the db template as possible. Will add filtering later --- backend/src/routes/game.ts | 60 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 backend/src/routes/game.ts diff --git a/backend/src/routes/game.ts b/backend/src/routes/game.ts new file mode 100644 index 0000000..c00518a --- /dev/null +++ b/backend/src/routes/game.ts @@ -0,0 +1,60 @@ +import { Hono } from "hono"; +import { eq, desc } from "drizzle-orm"; +import { db } from "../db"; +import { games } from "../db/schema"; + +/** + * Minimal requireSession middleware stub to satisfy imports and types. + * Replace with the real middleware implementation from ../lib/middleware when available. + */ +const requireSession = async (c: any, next: any) => { + return await next(); +}; + +const gamesRoute = new Hono(); + +/** + * GET /games + * + * Authenticated users only. + * Returns all ACTIVE games. + */ +gamesRoute.get("/", requireSession, async (c) => { + try { + const gamesList = await db.query.games.findMany({ + where: eq(games.isActive, true), + orderBy: (g, { desc }) => [desc(g.createdAt)], + columns: { + id: true, + title: true, + description: true, + gameUrl: true, + coverMediaId: true, + countLikes: true, + countDislikes: true, + countSuperlikes: true, + score: true, + viewCount: true, + createdBy: true, + }, + }); + + return c.json({ + success: true, + count: gamesList.length, + games: gamesList, + }); + } catch (error) { + console.error("Get games error:", error); + + return c.json( + { + error: "Failed to fetch games", + message: "An error occurred while retrieving games.", + }, + 500 + ); + } +}); + +export default gamesRoute; From 5b085b197b4eaa7efc0eb48c26c879369cc6d7be Mon Sep 17 00:00:00 2001 From: Sandy Date: Sat, 14 Feb 2026 17:07:28 +0530 Subject: [PATCH 02/10] Update game.ts small edits --- backend/src/routes/game.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/src/routes/game.ts b/backend/src/routes/game.ts index c00518a..619925c 100644 --- a/backend/src/routes/game.ts +++ b/backend/src/routes/game.ts @@ -3,10 +3,8 @@ import { eq, desc } from "drizzle-orm"; import { db } from "../db"; import { games } from "../db/schema"; -/** - * Minimal requireSession middleware stub to satisfy imports and types. - * Replace with the real middleware implementation from ../lib/middleware when available. - */ + + const requireSession = async (c: any, next: any) => { return await next(); }; @@ -18,6 +16,8 @@ const gamesRoute = new Hono(); * * Authenticated users only. * Returns all ACTIVE games. + * Pagination needs to be added + *Filtering system needs to be added */ gamesRoute.get("/", requireSession, async (c) => { try { @@ -58,3 +58,4 @@ gamesRoute.get("/", requireSession, async (c) => { }); export default gamesRoute; + From 96cda3dd39242e10988383c027065e071f9b666c Mon Sep 17 00:00:00 2001 From: Sannidhi Nayak Date: Fri, 20 Feb 2026 22:22:05 +0530 Subject: [PATCH 03/10] Enhance games route with pagination and filtering Updated the games route to include query parameters for pagination and filtering. Added zod validation. Tried to make it match the format of pre-existed profile and auth routes --- backend/src/routes/game.ts | 131 ++++++++++++++++++++++++++++++++----- 1 file changed, 115 insertions(+), 16 deletions(-) diff --git a/backend/src/routes/game.ts b/backend/src/routes/game.ts index 619925c..4c1f1fd 100644 --- a/backend/src/routes/game.ts +++ b/backend/src/routes/game.ts @@ -1,47 +1,147 @@ import { Hono } from "hono"; -import { eq, desc } from "drizzle-orm"; -import { db } from "../db"; +import { db } from "../db/index"; import { games } from "../db/schema"; +import { + eq, + like, + gte, + lte, + and, + desc, + count, +} from "drizzle-orm"; +import { z } from "zod"; +import { requireSession } from "../lib/middleware"; +const gamesRoute = new Hono(); +// ============================================================================ +// ZOD VALIDATION +// ============================================================================ -const requireSession = async (c: any, next: any) => { - return await next(); -}; - -const gamesRoute = new Hono(); +const gamesQuerySchema = z.object({ + page: z.coerce.number().min(1).default(1), + limit: z.coerce.number().min(1).max(50).default(10), + search: z.string().min(1).optional(), + minLikes: z.coerce.number().min(0).optional(), + maxLikes: z.coerce.number().min(0).optional(), + createdAfter: z.string().optional(), + createdBefore: z.string().optional(), +}); /** * GET /games * - * Authenticated users only. - * Returns all ACTIVE games. - * Pagination needs to be added - *Filtering system needs to be added + * gives all active games + * + * Requirements: + * - User must be authenticated + * + * Query parameters + * - page + * - limit + * - search + * - minLikes + * - maxLikes + * - createdAfter (YYYY-MM-DD) + * - createdBefore (YYYY-MM-DD) + * + */ gamesRoute.get("/", requireSession, async (c) => { try { + const queryParse = gamesQuerySchema.safeParse(c.req.query()); + + if (!queryParse.success) { + return c.json( + { + error: "Validation failed", + details: queryParse.error.format(), + }, + 400, + ); + } + + const { + page, + limit, + search, + minLikes, + maxLikes, + createdAfter, + createdBefore, + } = queryParse.data; + + const offset = (page - 1) * limit; +/*Filter conditions*/ + + const conditions = [eq(games.isActive, true)]; + + if (search) { + conditions.push(like(games.title, `%${search}%`)); + } + + if (minLikes !== undefined) { + conditions.push(gte(games.countLikes, minLikes)); + } + + if (maxLikes !== undefined) { + conditions.push(lte(games.countLikes, maxLikes)); + } + + if (createdAfter) { + const afterTimestamp = Math.floor( + new Date(createdAfter).getTime() / 1000, + ); + conditions.push(gte(games.createdAt, afterTimestamp)); + } + + if (createdBefore) { + const beforeTimestamp = Math.floor( + new Date(createdBefore).getTime() / 1000, + ); + conditions.push(lte(games.createdAt, beforeTimestamp)); + } + + const whereClause = and(...conditions); + +/*Data retrival*/ + const gamesList = await db.query.games.findMany({ - where: eq(games.isActive, true), + where: whereClause, orderBy: (g, { desc }) => [desc(g.createdAt)], + limit, + offset, columns: { id: true, title: true, description: true, gameUrl: true, coverMediaId: true, + createdBy: true, countLikes: true, countDislikes: true, countSuperlikes: true, score: true, viewCount: true, - createdBy: true, + createdAt: true, + updatedAt: true, }, }); + const totalResult = await db + .select({ value: count() }) + .from(games) + .where(whereClause); + + const total = totalResult[0]?.value ?? 0; + return c.json({ success: true, - count: gamesList.length, + page, + limit, + total, + totalPages: Math.ceil(total / limit), games: gamesList, }); } catch (error) { @@ -52,10 +152,9 @@ gamesRoute.get("/", requireSession, async (c) => { error: "Failed to fetch games", message: "An error occurred while retrieving games.", }, - 500 + 500, ); } }); export default gamesRoute; - From 56108830f007a5e7d285892db3b897090a980e6d Mon Sep 17 00:00:00 2001 From: Sannidhi Nayak Date: Sat, 21 Feb 2026 19:34:30 +0530 Subject: [PATCH 04/10] Refactor game route to simplify query parameters Removed unnecessary queries --- backend/src/routes/game.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/backend/src/routes/game.ts b/backend/src/routes/game.ts index 4c1f1fd..5adb51d 100644 --- a/backend/src/routes/game.ts +++ b/backend/src/routes/game.ts @@ -15,15 +15,11 @@ import { requireSession } from "../lib/middleware"; const gamesRoute = new Hono(); -// ============================================================================ -// ZOD VALIDATION -// ============================================================================ - +// ZOD validation const gamesQuerySchema = z.object({ page: z.coerce.number().min(1).default(1), limit: z.coerce.number().min(1).max(50).default(10), search: z.string().min(1).optional(), - minLikes: z.coerce.number().min(0).optional(), maxLikes: z.coerce.number().min(0).optional(), createdAfter: z.string().optional(), createdBefore: z.string().optional(), @@ -66,7 +62,6 @@ gamesRoute.get("/", requireSession, async (c) => { page, limit, search, - minLikes, maxLikes, createdAfter, createdBefore, @@ -81,8 +76,7 @@ gamesRoute.get("/", requireSession, async (c) => { conditions.push(like(games.title, `%${search}%`)); } - if (minLikes !== undefined) { - conditions.push(gte(games.countLikes, minLikes)); + } if (maxLikes !== undefined) { @@ -158,3 +152,4 @@ gamesRoute.get("/", requireSession, async (c) => { }); export default gamesRoute; + From 230f469cc3fe176f7028d19d249483c3eb864657 Mon Sep 17 00:00:00 2001 From: Rex-8 <133750501+Rex-8@users.noreply.github.com> Date: Tue, 24 Feb 2026 14:46:18 +0530 Subject: [PATCH 05/10] fix: exporting-games Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- backend/src/routes/game.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/routes/game.ts b/backend/src/routes/game.ts index 5adb51d..d5a23f0 100644 --- a/backend/src/routes/game.ts +++ b/backend/src/routes/game.ts @@ -13,7 +13,7 @@ import { import { z } from "zod"; import { requireSession } from "../lib/middleware"; -const gamesRoute = new Hono(); +export const gamesRoute = new Hono(); // ZOD validation const gamesQuerySchema = z.object({ From f4874bff00bd6cd9a4b0b118baad42ef690f684a Mon Sep 17 00:00:00 2001 From: Sannidhi Nayak Date: Tue, 24 Feb 2026 18:26:29 +0530 Subject: [PATCH 06/10] Update backend/src/routes/game.ts zod date validation Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- backend/src/routes/game.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/routes/game.ts b/backend/src/routes/game.ts index d5a23f0..47ea006 100644 --- a/backend/src/routes/game.ts +++ b/backend/src/routes/game.ts @@ -21,8 +21,8 @@ const gamesQuerySchema = z.object({ limit: z.coerce.number().min(1).max(50).default(10), search: z.string().min(1).optional(), maxLikes: z.coerce.number().min(0).optional(), - createdAfter: z.string().optional(), - createdBefore: z.string().optional(), + createdAfter: z.string().date().optional(), + createdBefore: z.string().date().optional(), }); /** From 85f190fb9cf340bf6eb98f47ae7adda1c8a63870 Mon Sep 17 00:00:00 2001 From: Sannidhi Nayak Date: Tue, 24 Feb 2026 18:27:12 +0530 Subject: [PATCH 07/10] Update backend/src/routes/game.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- backend/src/routes/game.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/backend/src/routes/game.ts b/backend/src/routes/game.ts index 47ea006..d853fa1 100644 --- a/backend/src/routes/game.ts +++ b/backend/src/routes/game.ts @@ -76,9 +76,6 @@ gamesRoute.get("/", requireSession, async (c) => { conditions.push(like(games.title, `%${search}%`)); } - - } - if (maxLikes !== undefined) { conditions.push(lte(games.countLikes, maxLikes)); } From 1718845263de27d0da0ba944b58f5b11a8b32e96 Mon Sep 17 00:00:00 2001 From: Sannidhi Nayak Date: Tue, 24 Feb 2026 18:36:24 +0530 Subject: [PATCH 08/10] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- backend/src/routes/game.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/routes/game.ts b/backend/src/routes/game.ts index d853fa1..cc3a174 100644 --- a/backend/src/routes/game.ts +++ b/backend/src/routes/game.ts @@ -7,7 +7,6 @@ import { gte, lte, and, - desc, count, } from "drizzle-orm"; import { z } from "zod"; From 031295ecd0b3a93b082330806b43614e4c61ecec Mon Sep 17 00:00:00 2001 From: Sannidhi Nayak Date: Thu, 12 Mar 2026 20:14:41 +0530 Subject: [PATCH 09/10] refactor(games): replace page-based pagination with cursor-based scroll pagination --- backend/src/routes/game.ts | 102 +++++++++++++------------------------ 1 file changed, 34 insertions(+), 68 deletions(-) diff --git a/backend/src/routes/game.ts b/backend/src/routes/game.ts index cc3a174..59a5234 100644 --- a/backend/src/routes/game.ts +++ b/backend/src/routes/game.ts @@ -1,14 +1,7 @@ import { Hono } from "hono"; import { db } from "../db/index"; import { games } from "../db/schema"; -import { - eq, - like, - gte, - lte, - and, - count, -} from "drizzle-orm"; +import { eq, like, gte, lte, lt, and } from "drizzle-orm"; import { z } from "zod"; import { requireSession } from "../lib/middleware"; @@ -16,61 +9,53 @@ export const gamesRoute = new Hono(); // ZOD validation const gamesQuerySchema = z.object({ - page: z.coerce.number().min(1).default(1), limit: z.coerce.number().min(1).max(50).default(10), + cursor: z.coerce.number().optional(), search: z.string().min(1).optional(), maxLikes: z.coerce.number().min(0).optional(), - createdAfter: z.string().date().optional(), - createdBefore: z.string().date().optional(), + createdAfter: z.string().date().optional(), + createdBefore: z.string().date().optional(), }); /** * GET /games * - * gives all active games + * Returns all active games with cursor-based (scroll) pagination. * * Requirements: * - User must be authenticated * - * Query parameters - * - page - * - limit - * - search - * - minLikes - * - maxLikes - * - createdAfter (YYYY-MM-DD) - * - createdBefore (YYYY-MM-DD) + * Query parameters: + * - limit Number of results per page (default: 10, max: 50) + * - cursor createdAt timestamp of the last received item; omit for first page + * - search Filter by title + * - minLikes Filter by minimum like count + * - maxLikes Filter by maximum like count + * - createdAfter Return games created after this date (YYYY-MM-DD) + * - createdBefore Return games created before this date (YYYY-MM-DD) * - + * Response: + * - games[] Array of game objects + * - nextCursor Pass as `cursor` in the next request; null means no more results */ gamesRoute.get("/", requireSession, async (c) => { try { const queryParse = gamesQuerySchema.safeParse(c.req.query()); if (!queryParse.success) { - return c.json( - { - error: "Validation failed", - details: queryParse.error.format(), - }, - 400, - ); + return c.json({ error: "Validation failed", details: queryParse.error.format() }, 400); } - const { - page, - limit, - search, - maxLikes, - createdAfter, - createdBefore, - } = queryParse.data; - - const offset = (page - 1) * limit; -/*Filter conditions*/ + const { limit, cursor, search, maxLikes, createdAfter, createdBefore } = queryParse.data; + /* Filter conditions */ const conditions = [eq(games.isActive, true)]; + // cursor pagination - only fetch records older than the last seen item + if (cursor !== undefined) { + conditions.push(lt(games.createdAt, cursor)); + } + if (search) { conditions.push(like(games.title, `%${search}%`)); } @@ -80,28 +65,22 @@ gamesRoute.get("/", requireSession, async (c) => { } if (createdAfter) { - const afterTimestamp = Math.floor( - new Date(createdAfter).getTime() / 1000, - ); + const afterTimestamp = Math.floor(new Date(createdAfter).getTime() / 1000); conditions.push(gte(games.createdAt, afterTimestamp)); } if (createdBefore) { - const beforeTimestamp = Math.floor( - new Date(createdBefore).getTime() / 1000, - ); + const beforeTimestamp = Math.floor(new Date(createdBefore).getTime() / 1000); conditions.push(lte(games.createdAt, beforeTimestamp)); } const whereClause = and(...conditions); -/*Data retrival*/ - + /* Data retrieval - fetch one extra so we can tell if there's a next page */ const gamesList = await db.query.games.findMany({ where: whereClause, orderBy: (g, { desc }) => [desc(g.createdAt)], - limit, - offset, + limit: limit + 1, columns: { id: true, title: true, @@ -119,33 +98,20 @@ gamesRoute.get("/", requireSession, async (c) => { }, }); - const totalResult = await db - .select({ value: count() }) - .from(games) - .where(whereClause); - - const total = totalResult[0]?.value ?? 0; + const hasNextPage = gamesList.length > limit; + const results = hasNextPage ? gamesList.slice(0, limit) : gamesList; + const nextCursor = hasNextPage ? results[results.length - 1].createdAt : null; return c.json({ success: true, - page, limit, - total, - totalPages: Math.ceil(total / limit), - games: gamesList, + nextCursor, + games: results, }); } catch (error) { console.error("Get games error:", error); - - return c.json( - { - error: "Failed to fetch games", - message: "An error occurred while retrieving games.", - }, - 500, - ); + return c.json({ error: "Failed to fetch games", message: "An error occurred while retrieving games." }, 500); } }); export default gamesRoute; - From cf7a665ce7962dca8f9f5d24277786b1d9a50ed6 Mon Sep 17 00:00:00 2001 From: Sannidhi Nayak Date: Thu, 12 Mar 2026 20:16:47 +0530 Subject: [PATCH 10/10] refactor(games) : comment fix Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- backend/src/routes/game.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/routes/game.ts b/backend/src/routes/game.ts index 59a5234..1bdc2e5 100644 --- a/backend/src/routes/game.ts +++ b/backend/src/routes/game.ts @@ -7,7 +7,7 @@ import { requireSession } from "../lib/middleware"; export const gamesRoute = new Hono(); -// ZOD validation +// zod validation const gamesQuerySchema = z.object({ limit: z.coerce.number().min(1).max(50).default(10), cursor: z.coerce.number().optional(),