From 528e060ed67b7ebf06d03e47614ca4aeb17da513 Mon Sep 17 00:00:00 2001 From: Eva Decker Date: Wed, 18 Sep 2024 01:45:37 -0400 Subject: [PATCH] chore: Refactor mutations to use helper queries and add validators file (#73) --- convex/formFields.ts | 4 +--- convex/forms.ts | 27 +++++++++---------------- convex/helpers.ts | 25 +++++++++++++++++++++++ convex/quests.ts | 32 +++++++++-------------------- convex/schema.ts | 18 ++++------------- convex/users.ts | 47 ++++++++++++++++--------------------------- convex/usersQuests.ts | 35 ++++++++++---------------------- convex/validators.ts | 12 +++++++++++ 8 files changed, 89 insertions(+), 111 deletions(-) create mode 100644 convex/helpers.ts create mode 100644 convex/validators.ts diff --git a/convex/formFields.ts b/convex/formFields.ts index d527a5c..a9dd997 100644 --- a/convex/formFields.ts +++ b/convex/formFields.ts @@ -5,9 +5,7 @@ import { query } from "./_generated/server"; // https://docs.convex.dev/functions/validation export const getAllFieldsForForm = query({ - args: { - formId: v.id("forms"), - }, + args: { formId: v.id("forms") }, handler: async (ctx, args) => { return await ctx.db .query("formFields") diff --git a/convex/forms.ts b/convex/forms.ts index f27d5c8..c0e12cd 100644 --- a/convex/forms.ts +++ b/convex/forms.ts @@ -1,7 +1,7 @@ -import { getAuthUserId } from "@convex-dev/auth/server"; import { v } from "convex/values"; import { mutation, query } from "./_generated/server"; -import { jurisdictions } from "./schema"; +import { userMutation } from "./helpers"; +import { jurisdiction } from "./validators"; // TODO: Add `returns` value validation // https://docs.convex.dev/functions/validation @@ -51,7 +51,7 @@ export const generateUploadUrl = mutation(async (ctx) => { return await ctx.storage.generateUploadUrl(); }); -export const uploadPDF = mutation({ +export const uploadPDF = userMutation({ args: { formId: v.id("forms"), storageId: v.id("_storage") }, handler: async (ctx, args) => { return await ctx.db.patch(args.formId, { @@ -60,48 +60,39 @@ export const uploadPDF = mutation({ }, }); -export const createForm = mutation({ +export const createForm = userMutation({ args: { title: v.string(), - jurisdiction: jurisdictions, + jurisdiction: jurisdiction, formCode: v.optional(v.string()), }, handler: async (ctx, args) => { - const userId = await getAuthUserId(ctx); - if (userId === null) throw new Error("Not authenticated"); return await ctx.db.insert("forms", { title: args.title, jurisdiction: args.jurisdiction, formCode: args.formCode, - creationUser: userId, + creationUser: ctx.userId, }); }, }); -export const deleteForm = mutation({ +export const deleteForm = userMutation({ args: { formId: v.id("forms") }, handler: async (ctx, args) => { - const userId = await getAuthUserId(ctx); - if (userId === null) throw new Error("Not authenticated"); await ctx.db.patch(args.formId, { deletionTime: Date.now() }); }, }); -export const undeleteForm = mutation({ +export const undeleteForm = userMutation({ args: { formId: v.id("forms") }, handler: async (ctx, args) => { - const userId = await getAuthUserId(ctx); - if (userId === null) throw new Error("Not authenticated"); await ctx.db.patch(args.formId, { deletionTime: undefined }); }, }); -export const permanentlyDeleteForm = mutation({ +export const permanentlyDeleteForm = userMutation({ args: { formId: v.id("forms") }, handler: async (ctx, args) => { - const userId = await getAuthUserId(ctx); - if (userId === null) throw new Error("Not authenticated"); - // TODO: Delete form references in other tables // Delete the form diff --git a/convex/helpers.ts b/convex/helpers.ts new file mode 100644 index 0000000..95a94f9 --- /dev/null +++ b/convex/helpers.ts @@ -0,0 +1,25 @@ +import { getAuthUserId } from "@convex-dev/auth/server"; +import { + customCtx, + customMutation, + customQuery, +} from "convex-helpers/server/customFunctions"; +import { mutation, query } from "./_generated/server"; + +export const userQuery = customQuery( + query, + customCtx(async (ctx) => { + const userId = await getAuthUserId(ctx); + if (userId === null) throw new Error("Not authenticated"); + return { userId, ctx }; + }), +); + +export const userMutation = customMutation(mutation, { + args: {}, + input: async (ctx) => { + const userId = await getAuthUserId(ctx); + if (userId === null) throw new Error("Not authenticated"); + return { ctx: { userId }, args: {} }; + }, +}); diff --git a/convex/quests.ts b/convex/quests.ts index 141e709..c722573 100644 --- a/convex/quests.ts +++ b/convex/quests.ts @@ -1,7 +1,7 @@ -import { getAuthUserId } from "@convex-dev/auth/server"; import { v } from "convex/values"; -import { mutation, query } from "./_generated/server"; -import { jurisdictions } from "./schema"; +import { query } from "./_generated/server"; +import { userMutation } from "./helpers"; +import { jurisdiction } from "./validators"; // TODO: Add `returns` value validation // https://docs.convex.dev/functions/validation @@ -30,25 +30,20 @@ export const getQuest = query({ }, }); -export const createQuest = mutation({ - args: { title: v.string(), jurisdiction: v.optional(jurisdictions) }, +export const createQuest = userMutation({ + args: { title: v.string(), jurisdiction: v.optional(jurisdiction) }, handler: async (ctx, args) => { - const userId = await getAuthUserId(ctx); - if (userId === null) throw new Error("Not authenticated"); return await ctx.db.insert("quests", { title: args.title, jurisdiction: args.jurisdiction, - creationUser: userId, + creationUser: ctx.userId, }); }, }); -export const addQuestStep = mutation({ +export const addQuestStep = userMutation({ args: { questId: v.id("quests"), title: v.string(), body: v.string() }, handler: async (ctx, args) => { - const userId = await getAuthUserId(ctx); - if (userId === null) throw new Error("Not authenticated"); - const existingSteps = (await ctx.db.get(args.questId))?.steps ?? []; await ctx.db.patch(args.questId, { @@ -63,30 +58,23 @@ export const addQuestStep = mutation({ }, }); -export const deleteQuest = mutation({ +export const deleteQuest = userMutation({ args: { questId: v.id("quests") }, handler: async (ctx, args) => { - const userId = await getAuthUserId(ctx); - if (userId === null) throw new Error("Not authenticated"); await ctx.db.patch(args.questId, { deletionTime: Date.now() }); }, }); -export const undeleteQuest = mutation({ +export const undeleteQuest = userMutation({ args: { questId: v.id("quests") }, handler: async (ctx, args) => { - const userId = await getAuthUserId(ctx); - if (userId === null) throw new Error("Not authenticated"); await ctx.db.patch(args.questId, { deletionTime: undefined }); }, }); -export const permanentlyDeleteQuest = mutation({ +export const permanentlyDeleteQuest = userMutation({ args: { questId: v.id("quests") }, handler: async (ctx, args) => { - const userId = await getAuthUserId(ctx); - if (userId === null) throw new Error("Not authenticated"); - // Delete userQuests const userQuests = await ctx.db .query("usersQuests") diff --git a/convex/schema.ts b/convex/schema.ts index 74aeb7e..088452c 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -1,17 +1,7 @@ import { authTables } from "@convex-dev/auth/server"; import { defineSchema, defineTable } from "convex/server"; import { v } from "convex/values"; -import { JURISDICTIONS } from "./constants"; - -export const jurisdictions = v.union( - ...Object.keys(JURISDICTIONS).map((jurisdiction) => v.literal(jurisdiction)), -); - -export const themes = v.union( - v.literal("system"), - v.literal("light"), - v.literal("dark"), -); +import { jurisdiction, theme } from "./validators"; export default defineSchema({ ...authTables, @@ -30,7 +20,7 @@ export default defineSchema({ formCode: v.optional(v.string()), creationUser: v.id("users"), file: v.optional(v.id("_storage")), - jurisdiction: jurisdictions, + jurisdiction: jurisdiction, deletionTime: v.optional(v.number()), }), @@ -82,7 +72,7 @@ export default defineSchema({ quests: defineTable({ title: v.string(), creationUser: v.id("users"), - jurisdiction: v.optional(jurisdictions), + jurisdiction: v.optional(jurisdiction), deletionTime: v.optional(v.number()), steps: v.optional( v.array( @@ -111,7 +101,7 @@ export default defineSchema({ emailVerificationTime: v.optional(v.number()), isAnonymous: v.optional(v.boolean()), isMinor: v.optional(v.boolean()), - theme: v.optional(themes), + theme: v.optional(theme), }).index("email", ["email"]), /** diff --git a/convex/users.ts b/convex/users.ts index 0f8af3f..1eb3d8d 100644 --- a/convex/users.ts +++ b/convex/users.ts @@ -1,7 +1,7 @@ -import { getAuthUserId } from "@convex-dev/auth/server"; import { v } from "convex/values"; -import { mutation, query } from "./_generated/server"; -import { themes } from "./schema"; +import { query } from "./_generated/server"; +import { userMutation, userQuery } from "./helpers"; +import { theme } from "./validators"; // TODO: Add `returns` value validation // https://docs.convex.dev/functions/validation @@ -13,72 +13,59 @@ export const getAllUsers = query({ }, }); -export const getCurrentUser = query({ +export const getCurrentUser = userQuery({ args: {}, handler: async (ctx) => { - const userId = await getAuthUserId(ctx); - if (userId === null) return null; - return await ctx.db.get(userId); + return await ctx.db.get(ctx.userId); }, }); -export const setCurrentUserName = mutation({ +export const setCurrentUserName = userMutation({ args: { name: v.optional(v.string()) }, handler: async (ctx, args) => { - const userId = await getAuthUserId(ctx); - if (userId === null) throw new Error("Not authenticated"); - await ctx.db.patch(userId, { name: args.name }); + await ctx.db.patch(ctx.userId, { name: args.name }); }, }); -export const setCurrentUserIsMinor = mutation({ +export const setCurrentUserIsMinor = userMutation({ args: { isMinor: v.boolean() }, handler: async (ctx, args) => { - const userId = await getAuthUserId(ctx); - if (userId === null) throw new Error("Not authenticated"); - await ctx.db.patch(userId, { isMinor: args.isMinor }); + await ctx.db.patch(ctx.userId, { isMinor: args.isMinor }); }, }); -export const setUserTheme = mutation({ - args: { - theme: themes, - }, +export const setUserTheme = userMutation({ + args: { theme: theme }, handler: async (ctx, args) => { - const userId = await getAuthUserId(ctx); - if (userId === null) throw new Error("Not authenticated"); - await ctx.db.patch(userId, { theme: args.theme }); + await ctx.db.patch(ctx.userId, { theme: args.theme }); }, }); -export const deleteCurrentUser = mutation({ +export const deleteCurrentUser = userMutation({ args: {}, handler: async (ctx) => { - const userId = await getAuthUserId(ctx); - if (userId === null) throw new Error("Not authenticated"); - // Delete userQuests const userQuests = await ctx.db .query("usersQuests") - .withIndex("userId", (q) => q.eq("userId", userId)) + .withIndex("userId", (q) => q.eq("userId", ctx.userId)) .collect(); for (const userQuest of userQuests) await ctx.db.delete(userQuest._id); // Delete authAccounts const authAccounts = await ctx.db .query("authAccounts") - .withIndex("userIdAndProvider", (q) => q.eq("userId", userId)) + .withIndex("userIdAndProvider", (q) => q.eq("userId", ctx.userId)) .collect(); for (const account of authAccounts) await ctx.db.delete(account._id); // Delete authSessions const authSessions = await ctx.db .query("authSessions") - .withIndex("userId", (q) => q.eq("userId", userId)) + .withIndex("userId", (q) => q.eq("userId", ctx.userId)) .collect(); for (const session of authSessions) await ctx.db.delete(session._id); // Finally, delete the user - await ctx.db.delete(userId); + await ctx.db.delete(ctx.userId); }, }); diff --git a/convex/usersQuests.ts b/convex/usersQuests.ts index be2ceb5..6cd484d 100644 --- a/convex/usersQuests.ts +++ b/convex/usersQuests.ts @@ -1,19 +1,16 @@ -import { getAuthUserId } from "@convex-dev/auth/server"; import { v } from "convex/values"; -import { mutation, query } from "./_generated/server"; +import { query } from "./_generated/server"; +import { userMutation, userQuery } from "./helpers"; // TODO: Add `returns` value validation // https://docs.convex.dev/functions/validation -export const getQuestsForCurrentUser = query({ +export const getQuestsForCurrentUser = userQuery({ args: {}, handler: async (ctx, _args) => { - const userId = await getAuthUserId(ctx); - if (userId === null) throw new Error("Not authenticated"); - const userQuests = await ctx.db .query("usersQuests") - .withIndex("userId", (q) => q.eq("userId", userId)) + .withIndex("userId", (q) => q.eq("userId", ctx.userId)) .collect(); const quests = Promise.all( @@ -24,15 +21,12 @@ export const getQuestsForCurrentUser = query({ }, }); -export const getAvailableQuestsForUser = query({ +export const getAvailableQuestsForUser = userQuery({ args: {}, handler: async (ctx, _args) => { - const userId = await getAuthUserId(ctx); - if (userId === null) throw new Error("Not authenticated"); - const userQuests = await ctx.db .query("usersQuests") - .withIndex("userId", (q) => q.eq("userId", userId)) + .withIndex("userId", (q) => q.eq("userId", ctx.userId)) .collect(); const userQuestIds = userQuests.map((quest) => quest.questId); @@ -47,9 +41,7 @@ export const getAvailableQuestsForUser = query({ }); export const getQuestCount = query({ - args: { - questId: v.id("quests"), - }, + args: { questId: v.id("quests") }, handler: async (ctx, args) => { const quests = await ctx.db .query("usersQuests") @@ -60,25 +52,20 @@ export const getQuestCount = query({ }, }); -export const create = mutation({ - args: { - questId: v.id("quests"), - }, +export const create = userMutation({ + args: { questId: v.id("quests") }, handler: async (ctx, args) => { - const userId = await getAuthUserId(ctx); - if (userId === null) throw new Error("Not authenticated"); - // Check if quest already exists for user const existing = await ctx.db .query("usersQuests") - .withIndex("userId", (q) => q.eq("userId", userId)) + .withIndex("userId", (q) => q.eq("userId", ctx.userId)) .filter((q) => q.eq(q.field("questId"), args.questId)) .collect(); if (existing.length > 0) throw new Error("Quest already exists for user"); await ctx.db.insert("usersQuests", { - userId, + userId: ctx.userId, questId: args.questId, }); }, diff --git a/convex/validators.ts b/convex/validators.ts new file mode 100644 index 0000000..c7f78ad --- /dev/null +++ b/convex/validators.ts @@ -0,0 +1,12 @@ +import { v } from "convex/values"; +import { JURISDICTIONS } from "./constants"; + +export const jurisdiction = v.union( + ...Object.keys(JURISDICTIONS).map((jurisdiction) => v.literal(jurisdiction)), +); + +export const theme = v.union( + v.literal("system"), + v.literal("light"), + v.literal("dark"), +);