ProjectR Docs
Backend (Convex)

API — auth

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

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

query (14)

FunctionDescriptionArgs
dashboardGetUserDetailGet composed user detail for dashboard screens. Keeps getUserProfile narrow for edit forms and single-table reads.{ userId: v.string() }
dashboardGetUserStatsGet user statistics Note: Better Auth manages users table - stats come from custom tables{ dateFrom: v.optional(v.string()), dateTo: v.optional(v.string()), }
dashboardListUsersWithAggregatesDashboard user listing with per-user aggregates. Same filtering/pagination as getAllUsers, but enriches each row with: - totalStampsCollected (sum of currentStamps + completedCycles * campaign.totalStamps) - totalSpentCents (sum of orders.totalAmount * 100, parsed) - totalProductInteractions (count of productInteractions rows) Designed to power the user-management table without N+1 client fetches. Page size capped at 50 to keep per-page enrichment bounded.{ limit: v.optional(v.number()), cursor: v.optional(v.string()), role: v.optional( v.union( v.literal("apps"), v.literal("dashboard"), v.literal("admin"), v.…
dashboardSearchUsersByBeautyProfileSearch users by their linked data (beauty profile, etc.){ skinType: v.optional(v.string()), limit: v.optional(v.number()), }
getAllUserIdsGet all user IDs for bulk operations (e.g. notifications). Returns only userId strings — lightweight for large user bases. Requires dashboard access.{ role: v.optional( v.union( v.literal("apps"), v.literal("dashboard"), v.literal("admin"), v.literal("super_admin"), ), ), status: v.optional( v.union( v.li…
getAllUsersGet all users with pagination and filters Requires dashboard access{ limit: v.optional(v.number()), cursor: v.optional(v.string()), role: v.optional( v.union( v.literal("apps"), v.literal("dashboard"), v.literal("admin"), v.…
getUserByIdGet user by profile ID (public query for dashboard) Requires dashboard access{ id: v.id("userProfiles") }
getUserProfileGet user profile by userId (public query for dashboard) Requires dashboard access (dashboard, admin, super_admin roles){ userId: v.string() }
getUserProfileByUserIdGet current user's profile (public query for dashboard frontend) Returns profile data directly (not wrapped in { success, data }). Used by dashboard to fetch the current user's profile after Better Auth login. Auth is enforced server-side via safeGetAuthUser — no userId arg needed.{}
mobileGetCurrentUserGet current user profile with all fields Returns Better Auth user data merged with custom profile data (30+ fields){}
mobileGetShopifyCustomerStatusGet Shopify customer status for current user{}
mobileGetUserBeautyProfileGet user's beauty profile (linked to Better Auth user){}
mobileIsProfileCompleteCheck if user profile is complete Returns detailed completion status for onboarding tracking{}
searchUsersSearch users across all fields (name, email, phone, beauty profile) Requires dashboard access{ query: v.string(), limit: v.optional(v.number()), includeBeautyProfile: v.optional(v.boolean()), }

mutation (13)

FunctionDescriptionArgs
dashboardBanUserBan user (dashboard admin function) Sets accountStatus to 'suspended' and isActive to false{ userId: v.string(), reason: v.optional(v.string()), }
dashboardBulkUpdateUsersBulk update users (dashboard admin function) Update multiple users at once (e.g., ban all, change roles){ userIds: v.array(v.string()), action: v.union( v.literal("ban"), v.literal("unban"), v.literal("delete"), v.literal("restore"), v.literal("changeRole"), ),…
dashboardCompleteHardDeleteComplete Hard Delete User (Dashboard Admin Function) PERMANENTLY removes user from ALL tables including Better Auth tables. This frees up the phone number/email for reuse. Tables deleted from: - userProfiles (app profile) - beautyProfiles (skin profile) - Better Auth: user, session, account tables Use cases: - Testing: Need to recreate user with same phone number - GDPR: Complete data erasure request - Cleanup: Remove test accounts WARNING: This is IRREVERSIBLE. Use soft delete for normal operations.{ userId: v.string(), // Better Auth user ID (from userProfiles.userId) }
dashboardDeleteUserDelete user (dashboard admin function) Soft delete: sets accountStatus to 'deleted' and isActive to false{ userId: v.string(), hardDelete: v.optional(v.boolean()), // Only super_admin can hard delete }
dashboardDeleteUserBeautyProfileDelete user's beauty profile (admin action){ userId: v.string(), }
dashboardRestoreUserRestore soft-deleted user (dashboard admin function) Sets accountStatus back to 'active' and isActive to true{ userId: v.string(), }
dashboardUnbanUserUnban user (dashboard admin function) Sets accountStatus to 'active' and isActive to true{ userId: v.string(), }
dashboardUpdateUserUpdate user profile (dashboard admin function) Admins can update any user's profile fields{ userId: v.string(), updates: v.object({ firstName: v.optional(v.string()), lastName: v.optional(v.string()), fullName: v.optional(v.string()), email: v.opt…
dashboardUpdateUserRoleUpdate user role (dashboard admin function) Super admins can change any role, admins can only change to apps/dashboard{ userId: v.string(), role: v.union( v.literal("apps"), v.literal("dashboard"), v.literal("admin"), v.literal("super_admin"), ), }
mobileGetOrCreateProfileGet or create profile for current user Returns full profile with all fields Profile fields accepted: - firstName, lastName, fullName: Name fields - email: Optional email - dateOfBirth: YYYY-MM-DD format - gender: male|female|non-binary|other|prefer-not-to-say - country: 2-letter country code (MY, US, etc.) - occupation: User's occupation - skincareExperience: beginner|intermediate|advanced - brandFamiliarity: new_to_brand|tried_before|regular_user If profile exists and args are provided, updates those fields. If profile doesn't exist, creates with provided values. Sets profileCompleted=true when required fields are all present.{ // Core profile fields (onboarding) firstName: v.optional(v.string()), lastName: v.optional(v.string()), fullName: v.optional(v.string()), email: v.optiona…
mobileMarkOnboardingOfferedMark onboarding as offered for the current user (persistent, server-side). Called when the beauty quiz prompt is shown — returning users skip onboarding on any device once this is set. Idempotent.{}
mobileUpdateProfile—{ // Name fields firstName: v.optional(v.string()), lastName: v.optional(v.string()), fullName: v.optional(v.string()), // Better Auth compatibility: name …
requestAccountDeletionRequest account deletion (mobile self-service) Soft delete: sets accountStatus to 'deleted' and isActive to false The mobile app auto-signs out when mobileGetCurrentUser returns { deleted: true }{}

internalQuery (4)

FunctionDescriptionArgs
getAccountStatusByEmailPre-auth account-status lookup by email — mirror of getAccountStatusByPhone. Used by the email sign-in HTTP route to 403 suspended/deleted accounts before Better Auth issues a session (the dashboard signs in via email).{ email: v.string() }
getAccountStatusByPhoneGet account status by phone number (internal) Used by OTP send/verify flows to block deleted or suspended accounts before consuming iSMS credits or creating sessions. Returns: { accountStatus, isActive } | null (null = no profile = new user){ phoneNumber: v.string() }
getCurrentAuthUserGet current authenticated user (internal) Used by actions that need to verify auth before making external API calls. Actions can call this via ctx.runQuery to get the authenticated user's ID. Returns: { userId: string } | null{}
getUserRoleInternalGet user role by userId (internal) Used by public actions that need to check authorization after verifying auth. Actions can't access ctx.db directly, so they call this via ctx.runQuery. Returns null for banned/suspended/deleted profiles as well as missing ones, so every action-side role gate built on this query also enforces the active-profile check (mirrors requireDashboardAccess/requireAdminAccess). Returns: { role: string } | null{ userId: v.string() }

internalMutation (5)

FunctionDescriptionArgs
checkOtpRateLimitCheck OTP send rate limit Called by HTTP action before sending OTP via iSMS{ phoneNumber: v.string(), }
checkOtpVerifyRateLimitCheck OTP verify rate limit Called by HTTP action before forwarding a verify request to Better Auth. Complements the per-code attempt cap in auth.ts verifyOTP.{ phoneNumber: v.string() }
checkSignInEmailRateLimitCheck email sign-in rate limit Called by HTTP action before forwarding to Better Auth{ key: v.string() }
internalCompleteHardDeleteInternal Complete Hard Delete (for CLI/testing - no auth required) Use with: bunx convex run "auth/mutations:internalCompleteHardDelete" '{"userId": "..."}'{ userId: v.string(), }
patchUserLastLoginPatch userProfiles.lastLoginAt with the timestamp of a fresh Better Auth session. Called from the session.create.after hook so the dashboard listing query can read the denormalized field instead of looking up sessions per page user. Idempotent: only writes when the new timestamp is strictly greater than the stored one (avoids unnecessary patches on stale events).{ userId: v.string(), loginAt: v.number(), }

On this page