// packages/backend/convex/schema.ts
import { authTables } from '@convex-dev/auth/server';
import { defineSchema, defineTable } from 'convex/server';
import { v } from 'convex/values';
export default defineSchema({
...authTables,
users: defineTable({
name: v.optional(v.string()),
image: v.optional(v.string()),
email: v.optional(v.string()),
emailVerificationTime: v.optional(v.number()),
phone: v.optional(v.string()),
phoneVerificationTime: v.optional(v.number()),
isAnonymous: v.optional(v.boolean()),
// RBAC extension (Convex Auth replaced Clerk 2026-05-08):
role: v.optional(v.union(v.literal('admin'), v.literal('staff'), v.literal('student'))),
})
.index('email', ['email'])
.index('phone', ['phone']),
products: defineTable({
name: v.string(),
sku: v.string(),
price: v.number(),
status: v.union(v.literal('active'), v.literal('archived')),
}).index('by_sku', ['sku']),
formSubmissions: defineTable({
formType: v.union(
v.literal('student-info'),
v.literal('leave-request'),
v.literal('transfer'),
v.literal('hold'),
v.literal('change')
),
payload: v.any(),
contactPhone: v.optional(v.string()),
contactName: v.optional(v.string()),
larkStatus: v.union(v.literal('pending'), v.literal('sent'), v.literal('failed')),
larkError: v.optional(v.string()),
larkAttempts: v.number(),
submittedAt: v.number(),
})
.index('by_type_and_submitted', ['formType', 'submittedAt'])
.index('by_lark_status', ['larkStatus'])
.index('by_phone', ['contactPhone']),
rooms: defineTable({
slug: v.string(),
kind: v.union(v.literal('one-on-one'), v.literal('class')),
hostUserId: v.id('users'),
status: v.union(v.literal('open'), v.literal('closed')),
closedReason: v.optional(v.string()),
}).index('by_slug', ['slug']),
roomPeers: defineTable({
roomId: v.id('rooms'),
userId: v.id('users'),
joinedAt: v.number(),
lastSeenAt: v.number(),
displayName: v.optional(v.string()),
isHost: v.boolean(),
status: v.union(v.literal('joined'), v.literal('left')),
})
.index('by_room', ['roomId'])
.index('by_user', ['userId']),
signalingMessages: defineTable({
roomId: v.id('rooms'),
fromUserId: v.id('users'),
toUserId: v.id('users'),
type: v.union(v.literal('offer'), v.literal('answer'), v.literal('ice')),
payload: v.any(),
expiresAt: v.number(),
})
.index('by_room_and_to_and_creation', ['roomId', 'toUserId'])
.index('by_expires', ['expiresAt']),
});