import { query, mutation } from "./_generated/server"; import { v } from "convex/values"; import * as Users from "./model/users"; const addressTypeValidator = v.union( v.literal("shipping"), v.literal("billing"), ); const addressFieldsValidator = { type: addressTypeValidator, fullName: v.optional(v.string()), firstName: v.string(), lastName: v.string(), phone: v.string(), addressLine1: v.string(), additionalInformation: v.optional(v.string()), city: v.string(), postalCode: v.string(), country: v.string(), isDefault: v.boolean(), isValidated: v.optional(v.boolean()), }; export const list = query({ args: {}, handler: async (ctx) => { const user = await Users.getCurrentUserOrThrow(ctx); const addresses = await ctx.db .query("addresses") .withIndex("by_user", (q) => q.eq("userId", user._id)) .collect(); addresses.sort((a, b) => { if (a.isDefault !== b.isDefault) return a.isDefault ? -1 : 1; return b._creationTime - a._creationTime; }); return addresses; }, }); export const add = mutation({ args: addressFieldsValidator, handler: async (ctx, args) => { const user = await Users.getCurrentUserOrThrow(ctx); const existingOfType = await ctx.db .query("addresses") .withIndex("by_user_and_type", (q) => q.eq("userId", user._id).eq("type", args.type), ) .collect(); const isDefault = existingOfType.length === 0 ? true : args.isDefault; if (isDefault && existingOfType.length > 0) { for (const addr of existingOfType) { await ctx.db.patch(addr._id, { isDefault: false }); } } return await ctx.db.insert("addresses", { userId: user._id, type: args.type, fullName: args.fullName || `${args.firstName} ${args.lastName}`, firstName: args.firstName, lastName: args.lastName, phone: args.phone, addressLine1: args.addressLine1, additionalInformation: args.additionalInformation, city: args.city, postalCode: args.postalCode, country: args.country, isDefault, isValidated: args.isValidated ?? false, }); }, }); export const update = mutation({ args: { id: v.id("addresses"), type: v.optional(addressTypeValidator), fullName: v.optional(v.string()), firstName: v.optional(v.string()), lastName: v.optional(v.string()), phone: v.optional(v.string()), addressLine1: v.optional(v.string()), additionalInformation: v.optional(v.string()), city: v.optional(v.string()), postalCode: v.optional(v.string()), country: v.optional(v.string()), isDefault: v.optional(v.boolean()), isValidated: v.optional(v.boolean()), }, handler: async (ctx, args) => { const { id, ...updates } = args; const address = await ctx.db.get(id); if (!address) throw new Error("Address not found"); await Users.requireOwnership(ctx, address.userId); const patch: Record = {}; if (updates.type !== undefined) patch.type = updates.type; if (updates.fullName !== undefined) patch.fullName = updates.fullName; if (updates.firstName !== undefined) patch.firstName = updates.firstName; if (updates.lastName !== undefined) patch.lastName = updates.lastName; if (updates.phone !== undefined) patch.phone = updates.phone; if (updates.addressLine1 !== undefined) patch.addressLine1 = updates.addressLine1; if (updates.additionalInformation !== undefined) patch.additionalInformation = updates.additionalInformation; if (updates.city !== undefined) patch.city = updates.city; if (updates.postalCode !== undefined) patch.postalCode = updates.postalCode; if (updates.country !== undefined) patch.country = updates.country; if (updates.isDefault !== undefined) patch.isDefault = updates.isDefault; if (updates.isValidated !== undefined) patch.isValidated = updates.isValidated; if (Object.keys(patch).length === 0) return id; await ctx.db.patch(id, patch as any); return id; }, }); export const remove = mutation({ args: { id: v.id("addresses") }, handler: async (ctx, { id }) => { const address = await ctx.db.get(id); if (!address) throw new Error("Address not found"); await Users.requireOwnership(ctx, address.userId); if (address.isDefault) { const rest = await ctx.db .query("addresses") .withIndex("by_user", (q) => q.eq("userId", address.userId)) .filter((q) => q.neq(q.field("_id"), id)) .collect(); const first = rest[0]; if (first) await ctx.db.patch(first._id, { isDefault: true }); } await ctx.db.delete(id); }, }); export const setDefault = mutation({ args: { id: v.id("addresses") }, handler: async (ctx, { id }) => { const address = await ctx.db.get(id); if (!address) throw new Error("Address not found"); await Users.requireOwnership(ctx, address.userId); const all = await ctx.db .query("addresses") .withIndex("by_user", (q) => q.eq("userId", address.userId)) .collect(); for (const addr of all) { await ctx.db.patch(addr._id, { isDefault: addr._id === id }); } }, }); export const markValidated = mutation({ args: { id: v.id("addresses"), isValidated: v.boolean(), }, handler: async (ctx, { id, isValidated }) => { const address = await ctx.db.get(id); if (!address) throw new Error("Address not found"); await Users.requireOwnership(ctx, address.userId); await ctx.db.patch(id, { isValidated }); }, });