From 8c56d54b4f1ec972d3c8455ab2fd015ccb4d8a0f Mon Sep 17 00:00:00 2001 From: Ashutoshbind15 Date: Fri, 18 Oct 2024 02:32:59 +0530 Subject: [PATCH 1/2] add map reviews and some admin helpers --- app/header.tsx | 6 ++++ app/maps/review/page.tsx | 55 ++++++++++++++++++++++++++++++ app/play/page.tsx | 2 +- convex/maps.ts | 73 ++++++++++++++++++++++++++++++++++++---- convex/results.ts | 7 +++- convex/schema.ts | 6 +++- convex/users.ts | 13 +++++++ 7 files changed, 153 insertions(+), 9 deletions(-) create mode 100644 app/maps/review/page.tsx diff --git a/app/header.tsx b/app/header.tsx index aaf2cd5..352742c 100644 --- a/app/header.tsx +++ b/app/header.tsx @@ -26,6 +26,7 @@ export default function Header() { const { signOut } = useAuthActions(); const { isAuthenticated } = useConvexAuth(); const flags = useQuery(api.flags.getFlags); + const isAdminQuery = useQuery(api.users.isAdmin); return (
@@ -49,6 +50,11 @@ export default function Header() { )} + {isAdminQuery && ( + + + + )} {flags?.showTestPage && ( diff --git a/app/maps/review/page.tsx b/app/maps/review/page.tsx new file mode 100644 index 0000000..b4493c8 --- /dev/null +++ b/app/maps/review/page.tsx @@ -0,0 +1,55 @@ +"use client"; + +import { Map } from "@/app/map"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { api } from "@/convex/_generated/api"; +import { useMutation, useQuery } from "convex/react"; + +const Page = () => { + const isAdmin = useQuery(api.users.isAdmin); + const maps = useQuery(api.maps.getMaps, { isReviewed: false }); + const adminApprovalMutation = useMutation(api.maps.approveMap); + + if (isAdmin == true) { + return ( +
+

Review Maps

+
+ {maps?.map((map) => ( + + + + + + + + + + + + + + ))} +
+
+ ); + } + + return
Not an admin
; +}; + +export default Page; diff --git a/app/play/page.tsx b/app/play/page.tsx index db80928..e5175e7 100644 --- a/app/play/page.tsx +++ b/app/play/page.tsx @@ -16,7 +16,7 @@ import { useEffect, useState } from "react"; import { Skeleton } from "@/components/ui/skeleton"; export default function PlayPage() { - const maps = useQuery(api.maps.getMaps); + const maps = useQuery(api.maps.getMaps, {}); const userMapResults = useQuery(api.playerresults.getUserMapStatus); const mapCountResults = useQuery(api.playerresults.getMapsWins); diff --git a/convex/maps.ts b/convex/maps.ts index b1283ac..fe411ed 100644 --- a/convex/maps.ts +++ b/convex/maps.ts @@ -57,12 +57,10 @@ export const addMap = mutation({ throw new Error("User not authenticated"); } - const maps = await ctx.db.query("maps").collect(); - await ctx.db.insert("maps", { - level: maps.length + 1, grid: args.grid, submittedBy: userId, + isReviewed: false, }); }, }); @@ -82,6 +80,7 @@ export const seedMaps = internalMutation({ ctx.db.insert("maps", { level: idx + 1, grid: map.grid, + isReviewed: true, }); } }), @@ -90,9 +89,71 @@ export const seedMaps = internalMutation({ }); export const getMaps = query({ - args: {}, - handler: async (ctx) => { - return await ctx.db.query("maps").withIndex("by_level").collect(); + args: { + isReviewed: v.optional(v.boolean()), + }, + handler: async (ctx, args) => { + if (args.isReviewed !== undefined) { + // todo: take this out into a helper function + // if a manual query is made, check if the user is an admin + + const userId = await getAuthUserId(ctx); + if (userId === null) { + return null; + } + + const admins = await ctx.db.query("admins").collect(); + const isAdmin = admins.some((admin) => admin.userId === userId); + + if (isAdmin) { + return await ctx.db + .query("maps") + .filter((q) => q.eq(q.field("isReviewed"), args.isReviewed)) + .collect(); + } else { + return null; + } + } + + return await ctx.db + .query("maps") + .filter((q) => q.eq(q.field("isReviewed"), true)) + .collect(); + }, +}); + +export const approveMap = mutation({ + args: { + mapId: v.id("maps"), + }, + handler: async (ctx, args) => { + // todo: take this out into a helper function + + const userId = await getAuthUserId(ctx); + if (userId === null) { + throw new Error("Unauthorized"); + } + + const admins = await ctx.db.query("admins").collect(); + const isAdmin = admins.some((admin) => admin.userId === userId); + + if (!isAdmin) { + throw new Error("Unauthorized"); + } else { + const maps = await ctx.db.query("maps").collect(); + + const lastLevel = maps.reduce((acc, map) => { + if (map.level) { + return Math.max(acc, map.level); + } + return acc; + }, 0); + + await ctx.db.patch(args.mapId, { + isReviewed: true, + level: lastLevel + 1, + }); + } }, }); diff --git a/convex/results.ts b/convex/results.ts index a1c03ee..b916f08 100644 --- a/convex/results.ts +++ b/convex/results.ts @@ -80,7 +80,12 @@ export const updateResult = internalMutation({ const maps = await ctx.db.query("maps").collect(); - const lastLevel = maps.reduce((max, map) => Math.max(max, map.level), 0); + const lastLevel = maps.reduce((acc, map) => { + if (map.level) { + return Math.max(acc, map.level); + } + return acc; + }, 0); await ctx.runMutation(internal.leaderboard.updateRankings, { modelId: game.modelId, diff --git a/convex/schema.ts b/convex/schema.ts index 5d5a4f7..97fc31b 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -12,9 +12,10 @@ export default defineSchema({ status: v.union(v.literal("in_progress"), v.literal("completed")), }), maps: defineTable({ - level: v.number(), + level: v.optional(v.number()), grid: v.array(v.array(v.string())), submittedBy: v.optional(v.id("users")), + isReviewed: v.boolean(), }).index("by_level", ["level"]), scores: defineTable({ modelId: v.string(), @@ -61,4 +62,7 @@ export default defineSchema({ attempts: v.array(v.id("attempts")), hasWon: v.boolean(), }).index("by_mapId_userId", ["mapId", "userId"]), + admins: defineTable({ + userId: v.id("users"), + }), }); diff --git a/convex/users.ts b/convex/users.ts index e991574..9d25d0d 100644 --- a/convex/users.ts +++ b/convex/users.ts @@ -26,3 +26,16 @@ export const getUserOrNull = query({ return await ctx.db.get(userId); }, }); + +export const isAdmin = query({ + handler: async (ctx) => { + const userId = await getAuthUserId(ctx); + if (userId === null) { + return false; + } + + const admins = await ctx.db.query("admins").collect(); + + return admins.some((admin) => admin.userId === userId); + }, +}); From e83e0403a163c94bd094122416dc00c402a565b4 Mon Sep 17 00:00:00 2001 From: Ashutoshbind15 Date: Fri, 18 Oct 2024 02:47:40 +0530 Subject: [PATCH 2/2] correct an import --- app/maps/review/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/maps/review/page.tsx b/app/maps/review/page.tsx index b4493c8..5192350 100644 --- a/app/maps/review/page.tsx +++ b/app/maps/review/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { Map } from "@/app/map"; +import { Map } from "@/components/Map"; import { Button } from "@/components/ui/button"; import { Card,