diff --git a/convex/maps.ts b/convex/maps.ts index fdccccc..199d982 100644 --- a/convex/maps.ts +++ b/convex/maps.ts @@ -10,6 +10,7 @@ import { ZombieSurvival } from "../simulators/zombie-survival"; import { api, internal } from "./_generated/api"; import { runModel } from "../models"; import { getAuthUserId } from "@convex-dev/auth/server"; +import { adminMutationBuilder } from "./users"; const LEVELS = [ { @@ -148,23 +149,11 @@ export const addMap = mutation({ }, }); -export const publishMap = mutation({ +export const publishMap = adminMutationBuilder({ args: { map: v.array(v.array(v.string())), }, handler: async (ctx, args) => { - const userId = await getAuthUserId(ctx); - - if (!userId) { - throw new Error("User not authenticated"); - } - - const isAdmin = await ctx.runQuery(api.users.isAdmin); - - if (!isAdmin) { - throw new Error("Publishing maps is available only for admins"); - } - const maps = await ctx.db .query("maps") .filter((q) => q.neq("level", undefined)) @@ -175,7 +164,7 @@ export const publishMap = mutation({ await ctx.db.insert("maps", { grid: args.map, level: lastLevel + 1, - submittedBy: userId, + submittedBy: ctx.admin.id, isReviewed: true, }); }, @@ -218,10 +207,12 @@ export const getMaps = query({ return null; } - const admins = await ctx.db.query("admins").collect(); - const isAdmin = admins.some((admin) => admin.userId === userId); + const admin = await ctx.db + .query("admins") + .withIndex("by_userId", (q) => q.eq("userId", userId)) + .first(); - if (isAdmin) { + if (admin) { return await ctx.db .query("maps") .filter((q) => q.eq(q.field("isReviewed"), args.isReviewed)) @@ -238,38 +229,24 @@ export const getMaps = query({ }, }); -export const approveMap = mutation({ +export const approveMap = adminMutationBuilder({ 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 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); + 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, - }); - } + await ctx.db.patch(args.mapId, { + isReviewed: true, + level: lastLevel + 1, + }); }, }); diff --git a/convex/schema.ts b/convex/schema.ts index 97fc31b..6cabf20 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -64,5 +64,5 @@ export default defineSchema({ }).index("by_mapId_userId", ["mapId", "userId"]), admins: defineTable({ userId: v.id("users"), - }), + }).index("by_userId", ["userId"]), }); diff --git a/convex/users.ts b/convex/users.ts index 9d25d0d..b3c02ea 100644 --- a/convex/users.ts +++ b/convex/users.ts @@ -1,5 +1,10 @@ import { getAuthUserId } from "@convex-dev/auth/server"; -import { query } from "./_generated/server"; +import { mutation, query } from "./_generated/server"; +import { + customQuery, + customCtx, + customMutation, +} from "convex-helpers/server/customFunctions"; export const viewer = query({ args: {}, @@ -34,8 +39,63 @@ export const isAdmin = query({ return false; } - const admins = await ctx.db.query("admins").collect(); + const admin = await ctx.db + .query("admins") + .withIndex("by_userId", (q) => q.eq("userId", userId)) + .first(); - return admins.some((admin) => admin.userId === userId); + return !!admin; }, }); + +export const adminQueryBuilder = customQuery( + query, + customCtx(async (ctx) => { + const userId = await getAuthUserId(ctx); + + if (userId === null) { + throw new Error("Unauthorized"); + } + + const admin = await ctx.db + .query("admins") + .withIndex("by_userId", (q) => q.eq("userId", userId)) + .first(); + + if (!admin) { + throw new Error("Admin only invocation called from non-admin user"); + } + + return { + admin: { + id: admin.userId, + }, + }; + }), +); + +export const adminMutationBuilder = customMutation( + mutation, + customCtx(async (ctx) => { + const userId = await getAuthUserId(ctx); + + if (userId === null) { + throw new Error("Unauthorized"); + } + + const admin = await ctx.db + .query("admins") + .withIndex("by_userId", (q) => q.eq("userId", userId)) + .first(); + + if (!admin) { + throw new Error("Admin only invocation called from non-admin user"); + } + + return { + admin: { + id: admin.userId, + }, + }; + }), +); diff --git a/package.json b/package.json index d0810f5..64ff3ef 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "convex": "^1.16.0", + "convex-helpers": "^0.1.61", "date-fns": "^4.1.0", "lucide-react": "^0.453.0", "next": "14.2.5",