ProjectR Docs
Backend (Convex)

API — gamificationLoyalty

Generated catalog of Convex functions in convex/gamificationLoyalty/. Regenerated by `bun run docs:api`.

Auto-generated from source. Do not edit by hand — run bun run docs:api.

query (20)

FunctionDescriptionArgs
dashboardGetAllCampaignsGet all campaigns (admin){ includeInactive: v.optional(v.boolean()), limit: v.optional(v.number()), }
dashboardGetCampaignAnalyticsGet campaign analytics (admin){ campaignId: v.id("stampCampaigns"), }
dashboardGetLoyaltyActivityLoyalty activity, filtered and paginated on the server. The date range decides what is read, so an export or a date preset outside the newest rows still finds its data. getRecentActivities is kept, but not because anything reads it. Nothing in the dashboard or mobile calls it. It is a public function, so its name is deployed API surface, and Convex and Vercel do not deploy together (see the deploy-ordering note in the backend CLAUDE.md). A dashboard build already open in a browser would break the moment the function vanished. The standing rule against deleting zero-caller public exports without production call metrics applies too.{ dateFrom: v.optional(v.number()), dateTo: v.optional(v.number()), activityType: v.optional( v.union( v.literal("all"), v.literal("stamps"), v.literal("vouc…
dashboardGetUserVouchersGet vouchers for a specific user (Dashboard admin) Allows admins to view any user's vouchers by userId{ userId: v.string(), status: v.optional( v.union( v.literal("active"), v.literal("used"), v.literal("expired"), v.literal("cancelled") ) ), limit: v.optiona…
dashboardGetVoucherAnalyticsGet voucher analytics (admin){ dateFrom: v.optional(v.number()), dateTo: v.optional(v.number()), }
dashboardGetVoucherTemplateGet the FULL voucher template document for the dashboard edit form. getVoucherTemplateById returns publicTemplateView, which strips edit-only fields (startsAt, endsAt, combinesWith, appliesToIds, eligibleIds, bxgy*, maxDiscountAmount, excludeShippingRates, codePrefix, shopifyDiscountId, archivedAt). The edit form reads those for prefill — with the projection they are always undefined, so editing an existing template silently drops its validity window / combination rules / BXGY config / targeting on save. This dashboard-role-gated query returns the unprojected Doc for that form. Keep getVoucherTemplateById (projected) for list/table views that don't edit.{ templateId: v.id("voucherTemplates"), }
getActiveCampaignsGet all active stamp campaigns Returns campaigns that are active and within their date range{}
getAuditMetricsGet audit metrics (Dashboard) Key performance metrics for gamification system{ dateFrom: v.optional(v.number()), dateTo: v.optional(v.number()), }
getAuditSummaryGet audit summary (Dashboard) Overview of all gamification activities for a time period{ dateFrom: v.optional(v.number()), dateTo: v.optional(v.number()), }
getCampaignByIdGet campaign by ID{ campaignId: v.id("stampCampaigns"), }
getCampaignParticipantsGet campaign participants (Dashboard) Returns users participating in a specific campaign with their progress{ campaignId: v.id("stampCampaigns"), limit: v.optional(v.number()), offset: v.optional(v.number()), search: v.optional(v.string()), }
getDashboardStatsGet dashboard stats (Dashboard) Returns overall gamification statistics for the dashboard{}
getEnhancedUsersGet enhanced loyalty users (Dashboard) Returns paginated user list with campaign participation data{ paginationOpts: paginationOptsValidator, search: v.optional(v.string()), sortField: v.optional(v.union(v.literal("joined"), v.literal("name"))), sortDirect…
getStampHistoryGet stamp history for a user{ campaignId: v.optional(v.id("stampCampaigns")), limit: v.optional(v.number()), cursor: v.optional(v.number()), // createdAt timestamp }
getUserAuditSummaryGet user audit summary (Dashboard) Gamification activity summary for a specific user{ userId: v.string(), dateFrom: v.optional(v.number()), dateTo: v.optional(v.number()), }
getUserLoyaltyDetailAlias for getDashboardUserLoyaltyProfile Provided for frontend compatibility{ userId: v.string(), }
getUserStampHistoryGet stamp history for a specific user (Dashboard) Used by admin to view user's stamp activity{ userId: v.string(), campaignId: v.optional(v.id("stampCampaigns")), limit: v.optional(v.number()), offset: v.optional(v.number()), }
getUserStampProgressGet user's stamp progress for a specific campaign{ campaignId: v.id("stampCampaigns"), }
getVoucherTemplatesGet all voucher templates. Dashboard role required — templates contain targeting metadata that should not be exposed to unauthenticated callers. Mobile uses the projected shape via getMobileVouchers which only embeds discount values, not targeting.{ isActive: v.optional(v.boolean()), voucherType: v.optional( v.union( v.literal("fixed_amount"), v.literal("percentage"), v.literal("free_shipping"), v.lite…
mobileGetApplicableVouchersGet applicable vouchers for checkout Returns user's active vouchers with applicability status based on cart total. Used by mobile app to display vouchers that can be applied to current order.{ cartTotal: v.number(), }

mutation (19)

FunctionDescriptionArgs
bulkDeleteVoucherTemplatesBulk delete voucher templates (admin). Mirrors the single deleteVoucherTemplate guard: a template that has already issued vouchers is skipped (deactivate instead), not deleted.{ templateIds: v.array(v.id("voucherTemplates")), }
bulkDuplicateVoucherTemplatesBulk duplicate voucher templates (admin). Each copy is created inactive and unlinked from Shopify (so it does not collide with the source's discount node) — the admin re-syncs after editing.{ templateIds: v.array(v.id("voucherTemplates")), }
bulkSetVoucherTemplateStatusBulk activate/deactivate voucher templates (admin).{ templateIds: v.array(v.id("voucherTemplates")), isActive: v.boolean(), }
dashboardBulkDeleteCampaignsBulk delete campaigns (Dashboard) Allows admins to delete multiple campaigns at once. Supports both soft delete (deactivate) and hard delete (remove all data). For soft delete: campaigns are deactivated, user progress is preserved. For hard delete: campaigns and ALL associated data are permanently removed.{ campaignIds: v.array(v.id("stampCampaigns")), hardDelete: v.optional(v.boolean()), }
dashboardBulkUpdateCampaignsBulk update campaigns (Dashboard) Allows bulk activation/deactivation of multiple campaigns{ campaignIds: v.array(v.id("stampCampaigns")), action: v.union( v.literal("activate"), v.literal("deactivate"), v.literal("delete") ), }
dashboardCreateCampaignCreate stamp campaign (admin) Updated to match original GraphQL CreatePurchaseStampCampaignInput{
dashboardCreateVoucherTemplateCreate voucher template (admin){ title: v.string(), description: v.optional(v.string()), voucherType: v.union( v.literal("fixed_amount"), v.literal("percentage"), v.literal("free_shipping"…
dashboardDeleteCampaignDelete campaign (Dashboard) Soft delete - marks campaign as inactive{ campaignId: v.id("stampCampaigns"), hardDelete: v.optional(v.boolean()), }
dashboardDeleteUserVoucherDelete user voucher (Dashboard) Admin can cancel/delete a user's voucher{ voucherId: v.id("vouchers"), reason: v.optional(v.string()), }
dashboardSyncTemplateToShopifyManually push a voucher template's current settings to its Shopify campaign discount node (admin). Same enqueue as the automatic sync on template edit — this is the dashboard's "Sync now" button.{ templateId: v.id("voucherTemplates"), }
dashboardToggleCampaignStatusToggle campaign status (Dashboard) Toggles isActive status of a campaign{ campaignId: v.id("stampCampaigns"), }
dashboardUpdateCampaignUpdate stamp campaign (admin) Updated to match original schema with all fields{
dashboardUpdateVoucherTemplateUpdate voucher template (admin){ templateId: v.id("voucherTemplates"), title: v.optional(v.string()), description: v.optional(v.string()), voucherType: v.optional( v.union( v.literal("fixe…
deleteVoucherTemplateDelete voucher template (Dashboard) Only allows deletion if no vouchers have been issued from this template{ templateId: v.id("voucherTemplates"), }
markShakeRewardRevealedMark a shake reward as revealed (after user taps and sees the animation). Called by mobile app after shake-to-win animation completes.{ stampHistoryId: v.id("stampHistory") }
resetUserProgressReset user progress (Dashboard) Resets a user's progress for a specific campaign{ userId: v.string(), campaignId: v.id("stampCampaigns"), }
syncVoucherTemplatesSync voucher templates with Shopify (Dashboard) Creates/updates Shopify discount codes for active templates{}
testAwardStampTest award stamp (Internal/Debug) Manually award stamps to a user for testing{ userId: v.string(), campaignId: v.id("stampCampaigns"), stampsToAward: v.number(), reason: v.optional(v.string()), }
toggleVoucherTemplateStatusToggle voucher template active status (Dashboard){ templateId: v.id("voucherTemplates"), }

internalQuery (5)

FunctionDescriptionArgs
getActiveCampaignsInternalGet active campaigns (internal){}
getVoucherByCodeInternalLook up a voucher by code (internal). Used by prepareCheckout to enforce channel restrictions before forwarding the code to Shopify.{ code: v.string() }
getVoucherByIdInternalGet voucher by ID (internal) Used for checkout flow to validate and create Shopify discount codes{ voucherId: v.id("vouchers"), }
getVoucherTemplateByIdInternalGet voucher template by ID (internal) For use by actions that need template details{ templateId: v.id("voucherTemplates"), }
internalGetExpiredVouchersGet expired vouchers (internal - for cleanup){ batchSize: v.number(), }

internalMutation (6)

FunctionDescriptionArgs
collectStampCollect stamp for a purchase Core stamp collection with special position support Called by: HTTP endpoints, webhooks, mobile app{ userId: v.string(), campaignId: v.id("stampCampaigns"), orderId: v.optional(v.string()), orderNumber: v.optional(v.string()), purchaseAmount: v.optional(v.…
markExpiredVouchersMark expired vouchers{ voucherIds: v.array(v.id("vouchers")), }
markVouchersUsedFromWebhookMark vouchers as used when a Shopify order webhook fires (internal). Called from the orders/create webhook dispatcher with the discount codes present on the Shopify order. For each known voucher code: - If status === "active" → patch to "used", record usedChannel from the Shopify source_name, decrement activeVouchers counter - Otherwise → skip silently (idempotent across webhook retries) This catches the case where a user redeems at the Shopify storefront directly (bypassing applyVoucherToOrder), ensuring our DB stays in sync.{ codes: v.array(v.string()), shopifyOrderId: v.string(), sourceName: v.optional(v.string()), }
restoreVouchersForCancelledOrderRestore vouchers when their order is cancelled (internal). Called from processOrderCancelled inside the rewardsReversedAt idempotency guard. For each voucher marked used by this order: - Not yet expired → back to "active", clear usage fields, bump the activeVouchers counter, and re-attach the code to the template's shared campaign discount node on Shopify - Expired in the meantime → flip to "expired" (no counter change, code stays off Shopify){ shopifyOrderId: v.string(), }
setTemplateDiscountNodeStore the template's campaign discount node GID (campaign-node model: one Shopify discount per template, voucher codes attached to it).{ templateId: v.id("voucherTemplates"), shopifyDiscountId: v.string(), }
updateVoucherWithShopifyCode—{ voucherId: v.id("vouchers"), code: v.string(), shopifyDiscountId: v.string(), }

internalAction (1)

FunctionDescriptionArgs
processExpiredVouchersProcess expired vouchers (scheduled job) Marks vouchers as expired when past their expiry date Should run periodically (e.g., every hour){}

On this page