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)
| Function | Description | Args |
|---|---|---|
dashboardGetAllCampaigns | Get all campaigns (admin) | { includeInactive: v.optional(v.boolean()), limit: v.optional(v.number()), } |
dashboardGetCampaignAnalytics | Get campaign analytics (admin) | { campaignId: v.id("stampCampaigns"), } |
dashboardGetLoyaltyActivity | Loyalty 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… |
dashboardGetUserVouchers | Get 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… |
dashboardGetVoucherAnalytics | Get voucher analytics (admin) | { dateFrom: v.optional(v.number()), dateTo: v.optional(v.number()), } |
dashboardGetVoucherTemplate | Get 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"), } |
getActiveCampaigns | Get all active stamp campaigns Returns campaigns that are active and within their date range | {} |
getAuditMetrics | Get audit metrics (Dashboard) Key performance metrics for gamification system | { dateFrom: v.optional(v.number()), dateTo: v.optional(v.number()), } |
getAuditSummary | Get audit summary (Dashboard) Overview of all gamification activities for a time period | { dateFrom: v.optional(v.number()), dateTo: v.optional(v.number()), } |
getCampaignById | Get campaign by ID | { campaignId: v.id("stampCampaigns"), } |
getCampaignParticipants | Get 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()), } |
getDashboardStats | Get dashboard stats (Dashboard) Returns overall gamification statistics for the dashboard | {} |
getEnhancedUsers | Get 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… |
getStampHistory | Get stamp history for a user | { campaignId: v.optional(v.id("stampCampaigns")), limit: v.optional(v.number()), cursor: v.optional(v.number()), // createdAt timestamp } |
getUserAuditSummary | Get user audit summary (Dashboard) Gamification activity summary for a specific user | { userId: v.string(), dateFrom: v.optional(v.number()), dateTo: v.optional(v.number()), } |
getUserLoyaltyDetail | Alias for getDashboardUserLoyaltyProfile Provided for frontend compatibility | { userId: v.string(), } |
getUserStampHistory | Get 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()), } |
getUserStampProgress | Get user's stamp progress for a specific campaign | { campaignId: v.id("stampCampaigns"), } |
getVoucherTemplates | Get 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… |
mobileGetApplicableVouchers | Get 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)
| Function | Description | Args |
|---|---|---|
bulkDeleteVoucherTemplates | Bulk 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")), } |
bulkDuplicateVoucherTemplates | Bulk 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")), } |
bulkSetVoucherTemplateStatus | Bulk activate/deactivate voucher templates (admin). | { templateIds: v.array(v.id("voucherTemplates")), isActive: v.boolean(), } |
dashboardBulkDeleteCampaigns | Bulk 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()), } |
dashboardBulkUpdateCampaigns | Bulk 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") ), } |
dashboardCreateCampaign | Create stamp campaign (admin) Updated to match original GraphQL CreatePurchaseStampCampaignInput | { |
dashboardCreateVoucherTemplate | Create 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"… |
dashboardDeleteCampaign | Delete campaign (Dashboard) Soft delete - marks campaign as inactive | { campaignId: v.id("stampCampaigns"), hardDelete: v.optional(v.boolean()), } |
dashboardDeleteUserVoucher | Delete user voucher (Dashboard) Admin can cancel/delete a user's voucher | { voucherId: v.id("vouchers"), reason: v.optional(v.string()), } |
dashboardSyncTemplateToShopify | Manually 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"), } |
dashboardToggleCampaignStatus | Toggle campaign status (Dashboard) Toggles isActive status of a campaign | { campaignId: v.id("stampCampaigns"), } |
dashboardUpdateCampaign | Update stamp campaign (admin) Updated to match original schema with all fields | { |
dashboardUpdateVoucherTemplate | Update 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… |
deleteVoucherTemplate | Delete voucher template (Dashboard) Only allows deletion if no vouchers have been issued from this template | { templateId: v.id("voucherTemplates"), } |
markShakeRewardRevealed | Mark 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") } |
resetUserProgress | Reset user progress (Dashboard) Resets a user's progress for a specific campaign | { userId: v.string(), campaignId: v.id("stampCampaigns"), } |
syncVoucherTemplates | Sync voucher templates with Shopify (Dashboard) Creates/updates Shopify discount codes for active templates | {} |
testAwardStamp | Test 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()), } |
toggleVoucherTemplateStatus | Toggle voucher template active status (Dashboard) | { templateId: v.id("voucherTemplates"), } |
internalQuery (5)
| Function | Description | Args |
|---|---|---|
getActiveCampaignsInternal | Get active campaigns (internal) | {} |
getVoucherByCodeInternal | Look up a voucher by code (internal). Used by prepareCheckout to enforce channel restrictions before forwarding the code to Shopify. | { code: v.string() } |
getVoucherByIdInternal | Get voucher by ID (internal) Used for checkout flow to validate and create Shopify discount codes | { voucherId: v.id("vouchers"), } |
getVoucherTemplateByIdInternal | Get voucher template by ID (internal) For use by actions that need template details | { templateId: v.id("voucherTemplates"), } |
internalGetExpiredVouchers | Get expired vouchers (internal - for cleanup) | { batchSize: v.number(), } |
internalMutation (6)
| Function | Description | Args |
|---|---|---|
collectStamp | Collect 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.… |
markExpiredVouchers | Mark expired vouchers | { voucherIds: v.array(v.id("vouchers")), } |
markVouchersUsedFromWebhook | Mark 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()), } |
restoreVouchersForCancelledOrder | Restore 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(), } |
setTemplateDiscountNode | Store 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)
| Function | Description | Args |
|---|---|---|
processExpiredVouchers | Process expired vouchers (scheduled job) Marks vouchers as expired when past their expiry date Should run periodically (e.g., every hour) | {} |