Unified · Split
| @@ -9,6 +9,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; · 8 unmodified lines | |||
| 9 | import { Input } from "@/components/ui/input"; | 9 | import { Input } from "@/components/ui/input"; |
| 10 | import { useSession } from "@/lib/session-context"; | 10 | import { useSession } from "@/lib/session-context"; |
| 11 | import { convexApi } from "@/lib/convex-api"; | 11 | import { convexApi } from "@/lib/convex-api"; |
| 12 | import type { Id } from "../../../../convex/_generated/dataModel"; | ||
| 12 | 13 | ||
| 13 | type Role = "owner" | "admin" | "member" | "billing_admin"; | 14 | type Role = "owner" | "admin" | "member" | "billing_admin"; |
| 14 | 15 | ||
| @@ -52,14 +53,14 @@ export function MembersView() { · 37 unmodified lines | |||
| 52 | const [inviteRole, setInviteRole] = useState<Role>("member"); | 53 | const [inviteRole, setInviteRole] = useState<Role>("member"); |
| 53 | const [inviteState, setInviteState] = useState<"idle" | "sending" | "sent" | "failed">("idle"); | 54 | const [inviteState, setInviteState] = useState<"idle" | "sending" | "sent" | "failed">("idle"); |
| 54 | const [inviteMessage, setInviteMessage] = useState<string | null>(null); | 55 | const [inviteMessage, setInviteMessage] = useState<string | null>(null); |
| 55 | const [busyMemberAccountId, setBusyMemberAccountId] = useState<string | null>(null); | 56 | const [busyMemberAccountId, setBusyMemberAccountId] = useState<Id<"accounts"> | null>(null); |
| 56 | const [busyInviteId, setBusyInviteId] = useState<string | null>(null); | 57 | const [busyInviteId, setBusyInviteId] = useState<Id<"invites"> | null>(null); |
| 57 | 58 | ||
| 58 | const memberItems = members?.items ?? []; | 59 | const memberItems = members?.items ?? []; |
| 59 | const inviteItems = listInvites?.items ?? []; | 60 | const inviteItems = listInvites?.items ?? []; |
| 60 | const pendingInviteItems = inviteItems.filter((invite) => invite.status === "pending" || invite.status === "failed"); | 61 | const pendingInviteItems = inviteItems.filter((invite) => invite.status === "pending" || invite.status === "failed"); |
| 61 | const actorMembership = memberItems.find((member) => | 62 | const actorMembership = memberItems.find((member) => |
| 62 | context?.accountId ? String(member.accountId) === context.accountId : false, | 63 | context?.accountId ? member.accountId === context.accountId : false, |
| 63 | ); | 64 | ); |
| 64 | const actorRole = actorMembership?.role ?? null; | 65 | const actorRole = actorMembership?.role ?? null; |
| 65 | const canManageMembers = actorRole === "owner" || actorRole === "admin"; | 66 | const canManageMembers = actorRole === "owner" || actorRole === "admin"; |
| @@ -276,20 +277,20 @@ export function MembersView() { · 210 unmodified lines | |||
| 276 | <p className="text-xs text-muted-foreground">Role: {invite.role}</p> | 277 | <p className="text-xs text-muted-foreground">Role: {invite.role}</p> |
| 277 | <p className="text-xs text-muted-foreground">Status: {invite.status}</p> | 278 | <p className="text-xs text-muted-foreground">Status: {invite.status}</p> |
| 278 | </div> | 279 | </div> |
| 279 | <Button | 280 | <Button |
| 280 | variant="outline" | 281 | variant="outline" |
| 281 | size="sm" | 282 | size="sm" |
| 282 | className="h-8 text-xs" | 283 | className="h-8 text-xs" |
| 283 | disabled={!canManageMembers || busyInviteId === String(invite.id)} | 284 | disabled={!canManageMembers || busyInviteId === invite.id} |
| 284 | onClick={async () => { | 285 | onClick={async () => { |
| 285 | if (!typedOrganizationId) { | 286 | if (!typedOrganizationId) { |
| 286 | return; | 287 | return; |
| 287 | } | 288 | } |
| 288 | 289 | ||
| 289 | setBusyInviteId(String(invite.id)); | 290 | setBusyInviteId(invite.id); |
| 290 | try { | 291 | try { |
| 291 | await revokeInvite({ | 292 | await revokeInvite({ |
| 292 | organizationId: typedOrganizationId, | 293 | organizationId: typedOrganizationId, |
| 293 | inviteId: invite.id, | 294 | inviteId: invite.id, |
| 294 | sessionId: context?.sessionId ?? undefined, | 295 | sessionId: context?.sessionId ?? undefined, |
| 295 | }); | 296 | }); |
| @@ -30,20 +30,20 @@ interface SessionState { · 29 unmodified lines | |||
| 30 | } | null; | 30 | } | null; |
| 31 | mode: "guest" | "workos"; | 31 | mode: "guest" | "workos"; |
| 32 | organizations: Array<{ | 32 | organizations: Array<{ |
| 33 | id: string; | 33 | id: Id<"organizations">; |
| 34 | name: string; | 34 | name: string; |
| 35 | slug: string; | 35 | slug: string; |
| 36 | status: string; | 36 | status: string; |
| 37 | role: string; | 37 | role: string; |
| 38 | }>; | 38 | }>; |
| 39 | workspaces: Array<{ | 39 | workspaces: Array<{ |
| 40 | id: string; | 40 | id: Id<"workspaces">; |
| 41 | docId: Id<"workspaces"> | null; | 41 | docId: Id<"workspaces"> | null; |
| 42 | name: string; | 42 | name: string; |
| 43 | organizationId: Id<"organizations"> | null; | 43 | organizationId: Id<"organizations"> | null; |
| 44 | iconUrl?: string | null; | 44 | iconUrl?: string | null; |
| 45 | }>; | 45 | }>; |
| 46 | switchWorkspace: (workspaceId: string) => void; | 46 | switchWorkspace: (workspaceId: Id<"workspaces">) => void; |
| 47 | creatingWorkspace: boolean; | 47 | creatingWorkspace: boolean; |
| 48 | createWorkspace: (name: string, iconFile?: File | null) => Promise<void>; | 48 | createWorkspace: (name: string, iconFile?: File | null) => Promise<void>; |
| 49 | isSignedInToWorkos: boolean; | 49 | isSignedInToWorkos: boolean; |
| @@ -77,16 +77,16 @@ const ACTIVE_WORKSPACE_BY_ACCOUNT_KEY = "executor_active_workspace_by_account"; · 27 unmodified lines | |||
| 77 | 77 | ||
| 78 | function readWorkspaceByAccount() { | 78 | function readWorkspaceByAccount() { |
| 79 | const raw = localStorage.getItem(ACTIVE_WORKSPACE_BY_ACCOUNT_KEY); | 79 | const raw = localStorage.getItem(ACTIVE_WORKSPACE_BY_ACCOUNT_KEY); |
| 80 | if (!raw) return {} as Record<string, string>; | 80 | if (!raw) return {} as Record<string, Id<"workspaces">>; |
| 81 | try { | 81 | try { |
| 82 | const parsed = JSON.parse(raw) as Record<string, string>; | 82 | const parsed = JSON.parse(raw) as Record<string, Id<"workspaces">>; |
| 83 | return parsed ?? {}; | 83 | return parsed ?? {}; |
| 84 | } catch { | 84 | } catch { |
| 85 | return {}; | 85 | return {}; |
| 86 | } | 86 | } |
| 87 | } | 87 | } |
| 88 | 88 | ||
| 89 | function writeWorkspaceByAccount(value: Record<string, string>) { | 89 | function writeWorkspaceByAccount(value: Record<string, Id<"workspaces">>) { |
| 90 | localStorage.setItem(ACTIVE_WORKSPACE_BY_ACCOUNT_KEY, JSON.stringify(value)); | 90 | localStorage.setItem(ACTIVE_WORKSPACE_BY_ACCOUNT_KEY, JSON.stringify(value)); |
| 91 | } | 91 | } |
| 92 | 92 | ||
| @@ -98,11 +98,12 @@ export function SessionProvider({ children }: { children: ReactNode }) { · 5 unmodified lines | |||
| 98 | } | 98 | } |
| 99 | return localStorage.getItem(SESSION_KEY); | 99 | return localStorage.getItem(SESSION_KEY); |
| 100 | }); | 100 | }); |
| 101 | const [activeWorkspaceId, setActiveWorkspaceId] = useState<string | null>(() => { | 101 | const [activeWorkspaceId, setActiveWorkspaceId] = useState<Id<"workspaces"> | null>(() => { |
| 102 | if (typeof window === "undefined") { | 102 | if (typeof window === "undefined") { |
| 103 | return null; | 103 | return null; |
| 104 | } | 104 | } |
| 105 | return localStorage.getItem(ACTIVE_WORKSPACE_KEY); | 105 | const stored = localStorage.getItem(ACTIVE_WORKSPACE_KEY); |
| 106 | return stored as Id<"workspaces"> | null; | ||
| 106 | }); | 107 | }); |
| 107 | const [runtimeError, setRuntimeError] = useState<string | null>(null); | 108 | const [runtimeError, setRuntimeError] = useState<string | null>(null); |
| 108 | 109 | ||
| @@ -148,17 +149,17 @@ export function SessionProvider({ children }: { children: ReactNode }) { · 39 unmodified lines | |||
| 148 | return activeWorkspaceId; | 149 | return activeWorkspaceId; |
| 149 | } | 150 | } |
| 150 | 151 | ||
| 151 | if (activeWorkspaceId && workspaces.some((workspace) => workspace.runtimeWorkspaceId === activeWorkspaceId)) { | 152 | if (activeWorkspaceId && workspaces.some((workspace) => workspace._id === activeWorkspaceId)) { |
| 152 | return activeWorkspaceId; | 153 | return activeWorkspaceId; |
| 153 | } | 154 | } |
| 154 | 155 | ||
| 155 | const accountId = account?.provider === "workos" ? String(account._id) : null; | 156 | const accountId = account?.provider === "workos" ? account._id : null; |
| 156 | const accountStoredWorkspace = accountId ? readWorkspaceByAccount()[accountId] : null; | 157 | const accountStoredWorkspace = accountId ? readWorkspaceByAccount()[accountId] : null; |
| 157 | if (accountStoredWorkspace && workspaces.some((workspace) => workspace.runtimeWorkspaceId === accountStoredWorkspace)) { | 158 | if (accountStoredWorkspace && workspaces.some((workspace) => workspace._id === accountStoredWorkspace)) { |
| 158 | return accountStoredWorkspace; | 159 | return accountStoredWorkspace; |
| 159 | } | 160 | } |
| 160 | 161 | ||
| 161 | return workspaces[0]?.runtimeWorkspaceId ?? null; | 162 | return workspaces[0]?._id ?? null; |
| 162 | }, [workspaces, activeWorkspaceId, account]); | 163 | }, [workspaces, activeWorkspaceId, account]); |
| 163 | 164 | ||
| 164 | const bootstrapWorkosAccountQuery = useTanstackQuery({ | 165 | const bootstrapWorkosAccountQuery = useTanstackQuery({ |
| @@ -177,12 +178,12 @@ export function SessionProvider({ children }: { children: ReactNode }) { · 12 unmodified lines | |||
| 177 | setRuntimeError(null); | 178 | setRuntimeError(null); |
| 178 | }, []); | 179 | }, []); |
| 179 | 180 | ||
| 180 | const switchWorkspace = useCallback((workspaceId: string) => { | 181 | const switchWorkspace = useCallback((workspaceId: Id<"workspaces">) => { |
| 181 | setActiveWorkspaceId(workspaceId); | 182 | setActiveWorkspaceId(workspaceId); |
| 182 | localStorage.setItem(ACTIVE_WORKSPACE_KEY, workspaceId); | 183 | localStorage.setItem(ACTIVE_WORKSPACE_KEY, workspaceId); |
| 183 | 184 | ||
| 184 | if (account?.provider === "workos") { | 185 | if (account?.provider === "workos") { |
| 185 | const accountId = String(account._id); | 186 | const accountId = account._id; |
| 186 | const byAccount = readWorkspaceByAccount(); | 187 | const byAccount = readWorkspaceByAccount(); |
| 187 | writeWorkspaceByAccount({ | 188 | writeWorkspaceByAccount({ |
| 188 | ...byAccount, | 189 | ...byAccount, |
| @@ -227,8 +228,8 @@ export function SessionProvider({ children }: { children: ReactNode }) { · 38 unmodified lines | |||
| 227 | sessionId: storedSessionId ?? undefined, | 228 | sessionId: storedSessionId ?? undefined, |
| 228 | }); | 229 | }); |
| 229 | 230 | ||
| 230 | if (created?.runtimeWorkspaceId) { | 231 | if (created?._id) { |
| 231 | switchWorkspace(created.runtimeWorkspaceId); | 232 | switchWorkspace(created._id); |
| 232 | } | 233 | } |
| 233 | } catch (cause) { | 234 | } catch (cause) { |
| 234 | const message = cause instanceof Error ? cause.message : "Failed to create workspace"; | 235 | const message = cause instanceof Error ? cause.message : "Failed to create workspace"; |
| @@ -250,7 +251,7 @@ export function SessionProvider({ children }: { children: ReactNode }) { · 15 unmodified lines | |||
| 250 | } | 251 | } |
| 251 | 252 | ||
| 252 | const activeWorkspace = | 253 | const activeWorkspace = |
| 253 | workspaces.find((workspace) => workspace.runtimeWorkspaceId === resolvedActiveWorkspaceId) | 254 | workspaces.find((workspace) => workspace._id === resolvedActiveWorkspaceId) |
| 254 | ?? workspaces[0] | 255 | ?? workspaces[0] |
| 255 | ?? null; | 256 | ?? null; |
| 256 | if (!activeWorkspace) { | 257 | if (!activeWorkspace) { |
| @@ -258,13 +259,12 @@ export function SessionProvider({ children }: { children: ReactNode }) { · 1 unmodified line | |||
| 258 | } | 259 | } |
| 259 | 260 | ||
| 260 | return { | 261 | return { |
| 261 | sessionId: `workos_${String(account._id)}`, | 262 | sessionId: `workos_${account._id}`, |
| 262 | workspaceId: activeWorkspace.runtimeWorkspaceId, | 263 | workspaceId: activeWorkspace._id, |
| 263 | actorId: String(activeWorkspace.userId), | 264 | actorId: activeWorkspace.userId, |
| 264 | clientId: "web", | 265 | clientId: "web", |
| 265 | accountId: String(account._id), | 266 | accountId: account._id, |
| 266 | workspaceDocId: String(activeWorkspace._id), | 267 | userId: activeWorkspace.userId, |
| 267 | userId: String(activeWorkspace.userId), | ||
| 268 | createdAt: Date.now(), | 268 | createdAt: Date.now(), |
| 269 | lastSeenAt: Date.now(), | 269 | lastSeenAt: Date.now(), |
| 270 | }; | 270 | }; |
| @@ -301,7 +301,7 @@ export function SessionProvider({ children }: { children: ReactNode }) { · 30 unmodified lines | |||
| 301 | const workspaceOptions = useMemo(() => { | 301 | const workspaceOptions = useMemo(() => { |
| 302 | if (mode === "workos" && workspaces) { | 302 | if (mode === "workos" && workspaces) { |
| 303 | return workspaces.map((workspace): SessionState["workspaces"][number] => ({ | 303 | return workspaces.map((workspace): SessionState["workspaces"][number] => ({ |
| 304 | id: workspace.runtimeWorkspaceId, | 304 | id: workspace._id, |
| 305 | docId: workspace._id, | 305 | docId: workspace._id, |
| 306 | name: workspace.name, | 306 | name: workspace.name, |
| 307 | organizationId: workspace.organizationId ?? null, | 307 | organizationId: workspace.organizationId ?? null, |
| @@ -312,7 +312,7 @@ export function SessionProvider({ children }: { children: ReactNode }) { · 4 unmodified lines | |||
| 312 | if (guestContext) { | 312 | if (guestContext) { |
| 313 | return [ | 313 | return [ |
| 314 | { | 314 | { |
| 315 | id: guestContext.workspaceId, | 315 | id: guestContext.workspaceId as Id<"workspaces">, |
| 316 | docId: null, | 316 | docId: null, |
| 317 | name: "Guest Workspace", | 317 | name: "Guest Workspace", |
| 318 | organizationId: null, | 318 | organizationId: null, |
| @@ -9,6 +9,8 @@ import { ensureUniqueSlug } from "./lib/slug"; · 8 unmodified lines | |||
| 9 | type DbCtx = Pick<MutationCtx, "db">; | 9 | type DbCtx = Pick<MutationCtx, "db">; |
| 10 | type RunQueryCtx = Pick<MutationCtx, "runQuery">; | 10 | type RunQueryCtx = Pick<MutationCtx, "runQuery">; |
| 11 | type WorkosEventCtx = Pick<MutationCtx, "db" | "runQuery">; | 11 | type WorkosEventCtx = Pick<MutationCtx, "db" | "runQuery">; |
| 12 | type OrganizationRole = "owner" | "admin" | "member" | "billing_admin"; | ||
| 13 | type OrganizationMemberStatus = "active" | "pending" | "removed"; | ||
| 12 | 14 | ||
| 13 | const workosEnabled = Boolean( | 15 | const workosEnabled = Boolean( |
| 14 | process.env.WORKOS_CLIENT_ID && process.env.WORKOS_API_KEY && process.env.WORKOS_WEBHOOK_SECRET, | 16 | process.env.WORKOS_CLIENT_ID && process.env.WORKOS_API_KEY && process.env.WORKOS_WEBHOOK_SECRET, |
| @@ -81,8 +83,8 @@ async function upsertOrganizationMembership( · 66 unmodified lines | |||
| 81 | args: { | 83 | args: { |
| 82 | organizationId: Id<"organizations">; | 84 | organizationId: Id<"organizations">; |
| 83 | accountId: Id<"accounts">; | 85 | accountId: Id<"accounts">; |
| 84 | role: string; | 86 | role: OrganizationRole; |
| 85 | status: string; | 87 | status: OrganizationMemberStatus; |
| 86 | billable: boolean; | 88 | billable: boolean; |
| 87 | invitedByAccountId?: Id<"accounts">; | 89 | invitedByAccountId?: Id<"accounts">; |
| 88 | now: number; | 90 | now: number; |
| @@ -153,7 +155,7 @@ async function ensurePersonalWorkspace( · 64 unmodified lines | |||
| 153 | opts: { email: string; firstName?: string; workosUserId: string; now: number; workspaceName?: string }, | 155 | opts: { email: string; firstName?: string; workosUserId: string; now: number; workspaceName?: string }, |
| 154 | ) { | 156 | ) { |
| 155 | const memberships = await ctx.db | 157 | const memberships = await ctx.db |
| 156 | .query("users") | 158 | .query("workspaceMembers") |
| 157 | .withIndex("by_account", (q) => q.eq("accountId", accountId)) | 159 | .withIndex("by_account", (q) => q.eq("accountId", accountId)) |
| 158 | .collect(); | 160 | .collect(); |
| 159 | 161 | ||
| @@ -204,7 +206,7 @@ async function ensurePersonalWorkspace( · 44 unmodified lines | |||
| 204 | now: opts.now, | 206 | now: opts.now, |
| 205 | }); | 207 | }); |
| 206 | 208 | ||
| 207 | const userId = await ctx.db.insert("users", { | 209 | const userId = await ctx.db.insert("workspaceMembers", { |
| 208 | workspaceId, | 210 | workspaceId, |
| 209 | accountId, | 211 | accountId, |
| 210 | role: "owner", | 212 | role: "owner", |
| @@ -305,7 +307,7 @@ const workosEventHandlers: Record<string, (ctx: WorkosEventCtx, event: any) => P · 94 unmodified lines | |||
| 305 | if (!account) return; | 307 | if (!account) return; |
| 306 | 308 | ||
| 307 | const memberships = await ctx.db | 309 | const memberships = await ctx.db |
| 308 | .query("users") | 310 | .query("workspaceMembers") |
| 309 | .withIndex("by_account", (q) => q.eq("accountId", account._id)) | 311 | .withIndex("by_account", (q) => q.eq("accountId", account._id)) |
| 310 | .collect(); | 312 | .collect(); |
| 311 | for (const membership of memberships) { | 313 | for (const membership of memberships) { |
| @@ -394,7 +396,7 @@ const workosEventHandlers: Record<string, (ctx: WorkosEventCtx, event: any) => P · 82 unmodified lines | |||
| 394 | if (!workspace) return; | 396 | if (!workspace) return; |
| 395 | 397 | ||
| 396 | const members = await ctx.db | 398 | const members = await ctx.db |
| 397 | .query("users") | 399 | .query("workspaceMembers") |
| 398 | .withIndex("by_workspace", (q) => q.eq("workspaceId", workspace._id)) | 400 | .withIndex("by_workspace", (q) => q.eq("workspaceId", workspace._id)) |
| 399 | .collect(); | 401 | .collect(); |
| 400 | for (const member of members) { | 402 | for (const member of members) { |
| @@ -428,7 +430,7 @@ const workosEventHandlers: Record<string, (ctx: WorkosEventCtx, event: any) => P · 27 unmodified lines | |||
| 428 | const organizationId = workspace.organizationId; | 430 | const organizationId = workspace.organizationId; |
| 429 | 431 | ||
| 430 | const existing = await ctx.db | 432 | const existing = await ctx.db |
| 431 | .query("users") | 433 | .query("workspaceMembers") |
| 432 | .withIndex("by_workspace_account", (q) => q.eq("workspaceId", workspace._id).eq("accountId", account._id)) | 434 | .withIndex("by_workspace_account", (q) => q.eq("workspaceId", workspace._id).eq("accountId", account._id)) |
| 433 | .unique(); | 435 | .unique(); |
| 434 | 436 | ||
| @@ -463,7 +465,7 @@ const workosEventHandlers: Record<string, (ctx: WorkosEventCtx, event: any) => P · 28 unmodified lines | |||
| 463 | return; | 465 | return; |
| 464 | } | 466 | } |
| 465 | 467 | ||
| 466 | await ctx.db.insert("users", { | 468 | await ctx.db.insert("workspaceMembers", { |
| 467 | workspaceId: workspace._id, | 469 | workspaceId: workspace._id, |
| 468 | accountId: account._id, | 470 | accountId: account._id, |
| 469 | workosOrgMembershipId: event.data.id, | 471 | workosOrgMembershipId: event.data.id, |
| @@ -487,7 +489,7 @@ const workosEventHandlers: Record<string, (ctx: WorkosEventCtx, event: any) => P · 17 unmodified lines | |||
| 487 | }; | 489 | }; |
| 488 | 490 | ||
| 489 | let membership = await ctx.db | 491 | let membership = await ctx.db |
| 490 | .query("users") | 492 | .query("workspaceMembers") |
| 491 | .withIndex("by_workos_membership_id", (q) => q.eq("workosOrgMembershipId", data.id)) | 493 | .withIndex("by_workos_membership_id", (q) => q.eq("workosOrgMembershipId", data.id)) |
| 492 | .unique(); | 494 | .unique(); |
| 493 | 495 | ||
| @@ -506,7 +508,7 @@ const workosEventHandlers: Record<string, (ctx: WorkosEventCtx, event: any) => P · 12 unmodified lines | |||
| 506 | const workspaceId = workspace._id; | 508 | const workspaceId = workspace._id; |
| 507 | const accountId = account._id; | 509 | const accountId = account._id; |
| 508 | membership = await ctx.db | 510 | membership = await ctx.db |
| 509 | .query("users") | 511 | .query("workspaceMembers") |
| 510 | .withIndex("by_workspace_account", (q) => q.eq("workspaceId", workspaceId).eq("accountId", accountId)) | 512 | .withIndex("by_workspace_account", (q) => q.eq("workspaceId", workspaceId).eq("accountId", accountId)) |
| 511 | .unique(); | 513 | .unique(); |
| 512 | if (!membership) return; | 514 | if (!membership) return; |
| @@ -551,7 +553,7 @@ const workosEventHandlers: Record<string, (ctx: WorkosEventCtx, event: any) => P · 38 unmodified lines | |||
| 551 | "organization_membership.deleted": async (ctx, event) => { | 553 | "organization_membership.deleted": async (ctx, event) => { |
| 552 | const now = Date.now(); | 554 | const now = Date.now(); |
| 553 | const membership = await ctx.db | 555 | const membership = await ctx.db |
| 554 | .query("users") | 556 | .query("workspaceMembers") |
| 555 | .withIndex("by_workos_membership_id", (q) => q.eq("workosOrgMembershipId", event.data.id)) | 557 | .withIndex("by_workos_membership_id", (q) => q.eq("workosOrgMembershipId", event.data.id)) |
| 556 | .unique(); | 558 | .unique(); |
| 557 | if (!membership) return; | 559 | if (!membership) return; |
| @@ -723,7 +725,7 @@ export const createWorkspace = mutation({ · 165 unmodified lines | |||
| 723 | now, | 725 | now, |
| 724 | }); | 726 | }); |
| 725 | 727 | ||
| 726 | const userId = await ctx.db.insert("users", { | 728 | const userId = await ctx.db.insert("workspaceMembers", { |
| 727 | workspaceId, | 729 | workspaceId, |
| 728 | accountId: account._id, | 730 | accountId: account._id, |
| 729 | role: "owner", | 731 | role: "owner", |
| @@ -750,7 +752,6 @@ export const createWorkspace = mutation({ · 20 unmodified lines | |||
| 750 | userId: user._id, | 752 | userId: user._id, |
| 751 | role: user.role, | 753 | role: user.role, |
| 752 | status: user.status, | 754 | status: user.status, |
| 753 | runtimeWorkspaceId: workspace._id, | ||
| 754 | }; | 755 | }; |
| 755 | }, | 756 | }, |
| 756 | }); | 757 | }); |
| @@ -804,7 +805,7 @@ export const getMyWorkspaces = query({ · 47 unmodified lines | |||
| 804 | if (!account) return []; | 805 | if (!account) return []; |
| 805 | 806 | ||
| 806 | const memberships = await ctx.db | 807 | const memberships = await ctx.db |
| 807 | .query("users") | 808 | .query("workspaceMembers") |
| 808 | .withIndex("by_account", (q) => q.eq("accountId", account._id)) | 809 | .withIndex("by_account", (q) => q.eq("accountId", account._id)) |
| 809 | .collect(); | 810 | .collect(); |
| 810 | 811 | ||
| @@ -823,7 +824,6 @@ export const getMyWorkspaces = query({ · 12 unmodified lines | |||
| 823 | userId: membership._id, | 824 | userId: membership._id, |
| 824 | role: membership.role, | 825 | role: membership.role, |
| 825 | status: membership.status, | 826 | status: membership.status, |
| 826 | runtimeWorkspaceId: workspace._id, | ||
| 827 | }; | 827 | }; |
| 828 | }), | 828 | }), |
| 829 | ); | 829 | ); |
| @@ -839,7 +839,7 @@ export const getMyAccountsWithWorkspaces = query({ · 9 unmodified lines | |||
| 839 | if (!account) return []; | 839 | if (!account) return []; |
| 840 | 840 | ||
| 841 | const memberships = await ctx.db | 841 | const memberships = await ctx.db |
| 842 | .query("users") | 842 | .query("workspaceMembers") |
| 843 | .withIndex("by_account", (q) => q.eq("accountId", account._id)) | 843 | .withIndex("by_account", (q) => q.eq("accountId", account._id)) |
| 844 | .collect(); | 844 | .collect(); |
| 845 | 845 | ||
| @@ -858,7 +858,6 @@ export const getMyAccountsWithWorkspaces = query({ · 12 unmodified lines | |||
| 858 | userId: membership._id, | 858 | userId: membership._id, |
| 859 | role: membership.role, | 859 | role: membership.role, |
| 860 | status: membership.status, | 860 | status: membership.status, |
| 861 | runtimeWorkspaceId: workspace._id, | ||
| 862 | }; | 861 | }; |
| 863 | }), | 862 | }), |
| 864 | ); | 863 | ); |
| @@ -5,6 +5,8 @@ import { mutation, query } from "./_generated/server"; · 4 unmodified lines | |||
| 5 | import { ensureUniqueSlug } from "./lib/slug"; | 5 | import { ensureUniqueSlug } from "./lib/slug"; |
| 6 | 6 | ||
| 7 | const DEFAULT_TIMEOUT_MS = 15_000; | 7 | const DEFAULT_TIMEOUT_MS = 15_000; |
| 8 | type OrganizationRole = "owner" | "admin" | "member" | "billing_admin"; | ||
| 9 | type OrganizationMemberStatus = "active" | "pending" | "removed"; | ||
| 8 | 10 | ||
| 9 | function normalizeOptional(value?: string): string { | 11 | function normalizeOptional(value?: string): string { |
| 10 | if (typeof value !== "string") { | 12 | if (typeof value !== "string") { |
| @@ -45,8 +47,8 @@ async function upsertOrganizationMembership( · 34 unmodified lines | |||
| 45 | args: { | 47 | args: { |
| 46 | organizationId: Doc<"organizations">["_id"]; | 48 | organizationId: Doc<"organizations">["_id"]; |
| 47 | accountId: Doc<"accounts">["_id"]; | 49 | accountId: Doc<"accounts">["_id"]; |
| 48 | role: string; | 50 | role: OrganizationRole; |
| 49 | status: string; | 51 | status: OrganizationMemberStatus; |
| 50 | billable: boolean; | 52 | billable: boolean; |
| 51 | now: number; | 53 | now: number; |
| 52 | }, | 54 | }, |
| @@ -168,7 +170,7 @@ async function ensureAnonymousIdentity( · 115 unmodified lines | |||
| 168 | ctx: MutationCtx, | 170 | ctx: MutationCtx, |
| 169 | params: { | 171 | params: { |
| 170 | sessionId: string; | 172 | sessionId: string; |
| 171 | workspaceDocId?: Doc<"workspaces">["_id"]; | 173 | workspaceId?: Doc<"workspaces">["_id"]; |
| 172 | actorId: string; | 174 | actorId: string; |
| 173 | timestamp: number; | 175 | timestamp: number; |
| 174 | }, | 176 | }, |
| @@ -199,7 +201,7 @@ async function ensureAnonymousIdentity( · 24 unmodified lines | |||
| 199 | await ctx.db.patch(account._id, { updatedAt: now, lastLoginAt: now }); | 201 | await ctx.db.patch(account._id, { updatedAt: now, lastLoginAt: now }); |
| 200 | } | 202 | } |
| 201 | 203 | ||
| 202 | let workspace = params.workspaceDocId ? await ctx.db.get(params.workspaceDocId) : null; | 204 | let workspace = params.workspaceId ? await ctx.db.get(params.workspaceId) : null; |
| 203 | 205 | ||
| 204 | let organizationId: Doc<"organizations">["_id"]; | 206 | let organizationId: Doc<"organizations">["_id"]; |
| 205 | 207 | ||
| @@ -241,12 +243,12 @@ async function ensureAnonymousIdentity( · 35 unmodified lines | |||
| 241 | }); | 243 | }); |
| 242 | 244 | ||
| 243 | let user = await ctx.db | 245 | let user = await ctx.db |
| 244 | .query("users") | 246 | .query("workspaceMembers") |
| 245 | .withIndex("by_workspace_account", (q) => q.eq("workspaceId", workspace._id).eq("accountId", account._id)) | 247 | .withIndex("by_workspace_account", (q) => q.eq("workspaceId", workspace._id).eq("accountId", account._id)) |
| 246 | .unique(); | 248 | .unique(); |
| 247 | 249 | ||
| 248 | if (!user) { | 250 | if (!user) { |
| 249 | const userId = await ctx.db.insert("users", { | 251 | const userId = await ctx.db.insert("workspaceMembers", { |
| 250 | workspaceId: workspace._id, | 252 | workspaceId: workspace._id, |
| 251 | accountId: account._id, | 253 | accountId: account._id, |
| 252 | role: "owner", | 254 | role: "owner", |
| @@ -264,7 +266,7 @@ async function ensureAnonymousIdentity( · 11 unmodified lines | |||
| 264 | 266 | ||
| 265 | return { | 267 | return { |
| 266 | accountId: account._id, | 268 | accountId: account._id, |
| 267 | workspaceDocId: workspace._id, | 269 | workspaceId: workspace._id, |
| 268 | userId: user._id, | 270 | userId: user._id, |
| 269 | }; | 271 | }; |
| 270 | } | 272 | } |
| @@ -276,7 +278,6 @@ function mapAnonymousContext(doc: Doc<"anonymousSessions">) { · 5 unmodified lines | |||
| 276 | actorId: doc.actorId, | 278 | actorId: doc.actorId, |
| 277 | clientId: doc.clientId, | 279 | clientId: doc.clientId, |
| 278 | accountId: doc.accountId, | 280 | accountId: doc.accountId, |
| 279 | workspaceDocId: doc.workspaceDocId, | ||
| 280 | userId: doc.userId, | 281 | userId: doc.userId, |
| 281 | createdAt: doc.createdAt, | 282 | createdAt: doc.createdAt, |
| 282 | lastSeenAt: doc.lastSeenAt, | 283 | lastSeenAt: doc.lastSeenAt, |
| @@ -708,15 +709,14 @@ export const bootstrapAnonymousSession = mutation({ · 425 unmodified lines | |||
| 708 | if (existing) { | 709 | if (existing) { |
| 709 | const identity = await ensureAnonymousIdentity(ctx, { | 710 | const identity = await ensureAnonymousIdentity(ctx, { |
| 710 | sessionId, | 711 | sessionId, |
| 711 | workspaceDocId: existing.workspaceDocId, | 712 | workspaceId: existing.workspaceId, |
| 712 | actorId: existing.actorId, | 713 | actorId: existing.actorId, |
| 713 | timestamp: now, | 714 | timestamp: now, |
| 714 | }); | 715 | }); |
| 715 | 716 | ||
| 716 | await ctx.db.patch(existing._id, { | 717 | await ctx.db.patch(existing._id, { |
| 717 | workspaceId: identity.workspaceDocId, | 718 | workspaceId: identity.workspaceId, |
| 718 | accountId: identity.accountId, | 719 | accountId: identity.accountId, |
| 719 | workspaceDocId: identity.workspaceDocId, | ||
| 720 | userId: identity.userId, | 720 | userId: identity.userId, |
| 721 | lastSeenAt: now, | 721 | lastSeenAt: now, |
| 722 | }); | 722 | }); |
| @@ -744,11 +744,10 @@ export const bootstrapAnonymousSession = mutation({ · 21 unmodified lines | |||
| 744 | 744 | ||
| 745 | await ctx.db.insert("anonymousSessions", { | 745 | await ctx.db.insert("anonymousSessions", { |
| 746 | sessionId, | 746 | sessionId, |
| 747 | workspaceId: identity.workspaceDocId, | 747 | workspaceId: identity.workspaceId, |
| 748 | actorId, | 748 | actorId, |
| 749 | clientId, | 749 | clientId, |
| 750 | accountId: identity.accountId, | 750 | accountId: identity.accountId, |
| 751 | workspaceDocId: identity.workspaceDocId, | ||
| 752 | userId: identity.userId, | 751 | userId: identity.userId, |
| 753 | createdAt: now, | 752 | createdAt: now, |
| 754 | lastSeenAt: now, | 753 | lastSeenAt: now, |
| @@ -7,6 +7,14 @@ import { authedMutation, organizationMutation, organizationQuery } from "./lib/f · 6 unmodified lines | |||
| 7 | 7 | ||
| 8 | const workosEnabled = Boolean(process.env.WORKOS_CLIENT_ID && process.env.WORKOS_API_KEY); | 8 | const workosEnabled = Boolean(process.env.WORKOS_CLIENT_ID && process.env.WORKOS_API_KEY); |
| 9 | const workosClient = process.env.WORKOS_API_KEY ? new WorkOS(process.env.WORKOS_API_KEY) : null; | 9 | const workosClient = process.env.WORKOS_API_KEY ? new WorkOS(process.env.WORKOS_API_KEY) : null; |
| 10 | type OrganizationRole = "owner" | "admin" | "member" | "billing_admin"; | ||
| 11 | |||
| 12 | const organizationRoleValidator = v.union( | ||
| 13 | v.literal("owner"), | ||
| 14 | v.literal("admin"), | ||
| 15 | v.literal("member"), | ||
| 16 | v.literal("billing_admin"), | ||
| 17 | ); | ||
| 10 | 18 | ||
| 11 | async function sha256Hex(value: string): Promise<string> { | 19 | async function sha256Hex(value: string): Promise<string> { |
| 12 | const bytes = new TextEncoder().encode(value); | 20 | const bytes = new TextEncoder().encode(value); |
| @@ -65,7 +73,7 @@ async function revokeWorkosInvitation(invitationId: string): Promise<void> { · 52 unmodified lines | |||
| 65 | await workos.userManagement.revokeInvitation(invitationId); | 73 | await workos.userManagement.revokeInvitation(invitationId); |
| 66 | } | 74 | } |
| 67 | 75 | ||
| 68 | function mapRoleToWorkosRoleSlug(role: string): string | undefined { | 76 | function mapRoleToWorkosRoleSlug(role: OrganizationRole): string | undefined { |
| 69 | if (role === "admin" || role === "owner") { | 77 | if (role === "admin" || role === "owner") { |
| 70 | return "admin"; | 78 | return "admin"; |
| 71 | } | 79 | } |
| @@ -103,7 +111,7 @@ export const create = organizationMutation({ · 31 unmodified lines | |||
| 103 | requireAdmin: true, | 111 | requireAdmin: true, |
| 104 | args: { | 112 | args: { |
| 105 | email: v.string(), | 113 | email: v.string(), |
| 106 | role: v.string(), | 114 | role: organizationRoleValidator, |
| 107 | workspaceId: v.optional(v.id("workspaces")), | 115 | workspaceId: v.optional(v.id("workspaces")), |
| 108 | expiresInDays: v.optional(v.number()), | 116 | expiresInDays: v.optional(v.number()), |
| 109 | }, | 117 | }, |
| @@ -123,17 +131,10 @@ export const create = organizationMutation({ · 13 unmodified lines | |||
| 123 | } | 131 | } |
| 124 | } | 132 | } |
| 125 | 133 | ||
| 126 | const inviterWorkosUserId = ctx.account.provider === "workos" | 134 | if (ctx.account.provider !== "workos") { |
| 127 | ? ctx.account.providerAccountId | ||
| 128 | : (await ctx.db | ||
| 129 | .query("accountIdentities") | ||
| 130 | .withIndex("by_account", (q) => q.eq("accountId", ctx.account._id)) | ||
| 131 | .filter((q) => q.eq(q.field("provider"), "workos")) | ||
| 132 | .first())?.providerUserId; | ||
| 133 | |||
| 134 | if (!inviterWorkosUserId) { | ||
| 135 | throw new Error("Inviter is not linked to WorkOS"); | 135 | throw new Error("Inviter is not linked to WorkOS"); |
| 136 | } | 136 | } |
| 137 | const inviterWorkosUserId = ctx.account.providerAccountId; | ||
| 137 | 138 | ||
| 138 | const token = `invite_${crypto.randomUUID()}`; | 139 | const token = `invite_${crypto.randomUUID()}`; |
| 139 | const tokenHash = await sha256Hex(token); | 140 | const tokenHash = await sha256Hex(token); |
| @@ -25,14 +25,6 @@ export async function resolveAccountForRequest( · 24 unmodified lines | |||
| 25 | if (fromAccounts) { | 25 | if (fromAccounts) { |
| 26 | return fromAccounts; | 26 | return fromAccounts; |
| 27 | } | 27 | } |
| 28 | |||
| 29 | const fromIdentity = await ctx.db | ||
| 30 | .query("accountIdentities") | ||
| 31 | .withIndex("by_provider_user", (q) => q.eq("provider", "workos").eq("providerUserId", identity.subject)) | ||
| 32 | .unique(); | ||
| 33 | if (fromIdentity?.accountId) { | ||
| 34 | return await ctx.db.get(fromIdentity.accountId); | ||
| 35 | } | ||
| 36 | } | 28 | } |
| 37 | 29 | ||
| 38 | if (!sessionId) { | 30 | if (!sessionId) { |
| @@ -3,6 +3,13 @@ import { internal } from "./_generated/api"; · 2 unmodified lines | |||
| 3 | import { getOrganizationMembership } from "./lib/identity"; | 3 | import { getOrganizationMembership } from "./lib/identity"; |
| 4 | import { organizationMutation, organizationQuery } from "./lib/functionBuilders"; | 4 | import { organizationMutation, organizationQuery } from "./lib/functionBuilders"; |
| 5 | 5 | ||
| 6 | const organizationRoleValidator = v.union( | ||
| 7 | v.literal("owner"), | ||
| 8 | v.literal("admin"), | ||
| 9 | v.literal("member"), | ||
| 10 | v.literal("billing_admin"), | ||
| 11 | ); | ||
| 12 | |||
| 6 | export const list = organizationQuery({ | 13 | export const list = organizationQuery({ |
| 7 | args: {}, | 14 | args: {}, |
| 8 | handler: async (ctx) => { | 15 | handler: async (ctx) => { |
| @@ -36,7 +43,7 @@ export const list = organizationQuery({ · 27 unmodified lines | |||
| 36 | export const updateRole = organizationMutation({ | 43 | export const updateRole = organizationMutation({ |
| 37 | args: { | 44 | args: { |
| 38 | accountId: v.id("accounts"), | 45 | accountId: v.id("accounts"), |
| 39 | role: v.string(), | 46 | role: organizationRoleValidator, |
| 40 | }, | 47 | }, |
| 41 | requireAdmin: true, | 48 | requireAdmin: true, |
| 42 | handler: async (ctx, args) => { | 49 | handler: async (ctx, args) => { |
| @@ -11,7 +11,6 @@ type WorkspaceSummary = { · 10 unmodified lines | |||
| 11 | name: string; | 11 | name: string; |
| 12 | slug: string; | 12 | slug: string; |
| 13 | iconUrl: string | null; | 13 | iconUrl: string | null; |
| 14 | runtimeWorkspaceId: string; | ||
| 15 | }; | 14 | }; |
| 16 | 15 | ||
| 17 | async function ensureUniqueOrganizationSlug(ctx: Pick<MutationCtx, "db">, baseName: string): Promise<string> { | 16 | async function ensureUniqueOrganizationSlug(ctx: Pick<MutationCtx, "db">, baseName: string): Promise<string> { |
| @@ -36,7 +35,6 @@ async function mapWorkspaceWithIcon( · 18 unmodified lines | |||
| 36 | name: workspace.name, | 35 | name: workspace.name, |
| 37 | slug: workspace.slug, | 36 | slug: workspace.slug, |
| 38 | iconUrl, | 37 | iconUrl, |
| 39 | runtimeWorkspaceId: workspace._id, | ||
| 40 | }; | 38 | }; |
| 41 | } | 39 | } |
| 42 | 40 | ||
| @@ -152,8 +150,8 @@ export const getNavigationState = optionalAccountQuery({ · 109 unmodified lines | |||
| 152 | .query("anonymousSessions") | 150 | .query("anonymousSessions") |
| 153 | .withIndex("by_session_id", (q) => q.eq("sessionId", sessionId)) | 151 | .withIndex("by_session_id", (q) => q.eq("sessionId", sessionId)) |
| 154 | .unique(); | 152 | .unique(); |
| 155 | if (anonymousSession?.workspaceDocId) { | 153 | if (anonymousSession?.workspaceId) { |
| 156 | const workspace = await ctx.db.get(anonymousSession.workspaceDocId); | 154 | const workspace = await ctx.db.get(anonymousSession.workspaceId); |
| 157 | if (workspace) { | 155 | if (workspace) { |
| 158 | workspaces.push(await mapWorkspaceWithIcon(ctx, workspace)); | 156 | workspaces.push(await mapWorkspaceWithIcon(ctx, workspace)); |
| 159 | } | 157 | } |
| @@ -162,7 +160,7 @@ export const getNavigationState = optionalAccountQuery({ · 2 unmodified lines | |||
| 162 | 160 | ||
| 163 | return { | 161 | return { |
| 164 | currentOrganizationId: workspaces[0]?.organizationId ?? null, | 162 | currentOrganizationId: workspaces[0]?.organizationId ?? null, |
| 165 | currentWorkspaceId: workspaces[0]?.runtimeWorkspaceId ?? null, | 163 | currentWorkspaceId: workspaces[0]?.id ?? null, |
| 166 | organizations, | 164 | organizations, |
| 167 | workspaces, | 165 | workspaces, |
| 168 | }; | 166 | }; |
| @@ -199,12 +197,12 @@ export const getNavigationState = optionalAccountQuery({ · 30 unmodified lines | |||
| 199 | } | 197 | } |
| 200 | 198 | ||
| 201 | const uniqueWorkspaces = Array.from( | 199 | const uniqueWorkspaces = Array.from( |
| 202 | new Map(workspaces.map((workspace) => [workspace.runtimeWorkspaceId, workspace])).values(), | 200 | new Map(workspaces.map((workspace) => [workspace.id, workspace])).values(), |
| 203 | ); | 201 | ); |
| 204 | 202 | ||
| 205 | return { | 203 | return { |
| 206 | currentOrganizationId: organizations[0]?.id ?? null, | 204 | currentOrganizationId: organizations[0]?.id ?? null, |
| 207 | currentWorkspaceId: uniqueWorkspaces[0]?.runtimeWorkspaceId ?? null, | 205 | currentWorkspaceId: uniqueWorkspaces[0]?.id ?? null, |
| 208 | organizations, | 206 | organizations, |
| 209 | workspaces: uniqueWorkspaces, | 207 | workspaces: uniqueWorkspaces, |
| 210 | }; | 208 | }; |
| @@ -1,16 +1,32 @@ | |||
| 1 | import { defineSchema, defineTable } from "convex/server"; | 1 | import { defineSchema, defineTable } from "convex/server"; |
| 2 | import { v } from "convex/values"; | 2 | import { v } from "convex/values"; |
| 3 | 3 | ||
| 4 | const accountProvider = v.union(v.literal("workos"), v.literal("anonymous")); | ||
| 5 | const accountStatus = v.union(v.literal("active"), v.literal("deleted")); | ||
| 6 | const organizationStatus = v.union(v.literal("active"), v.literal("deleted")); | ||
| 7 | const orgRole = v.union(v.literal("owner"), v.literal("admin"), v.literal("member"), v.literal("billing_admin")); | ||
| 8 | const orgMemberStatus = v.union(v.literal("active"), v.literal("pending"), v.literal("removed")); | ||
| 9 | const workspaceMemberRole = v.union(v.literal("owner"), v.literal("admin"), v.literal("member")); | ||
| 10 | const workspaceMemberStatus = v.union(v.literal("active"), v.literal("pending"), v.literal("removed")); | ||
| 11 | const inviteStatus = v.union( | ||
| 12 | v.literal("pending"), | ||
| 13 | v.literal("accepted"), | ||
| 14 | v.literal("expired"), | ||
| 15 | v.literal("revoked"), | ||
| 16 | v.literal("failed"), | ||
| 17 | ); | ||
| 18 | const inviteProvider = v.literal("workos"); | ||
| 19 | |||
| 4 | export default defineSchema({ | 20 | export default defineSchema({ |
| 5 | accounts: defineTable({ | 21 | accounts: defineTable({ |
| 6 | provider: v.string(), | 22 | provider: accountProvider, |
| 7 | providerAccountId: v.string(), | 23 | providerAccountId: v.string(), |
| 8 | email: v.string(), | 24 | email: v.string(), |
| 9 | name: v.string(), | 25 | name: v.string(), |
| 10 | firstName: v.optional(v.string()), | 26 | firstName: v.optional(v.string()), |
| 11 | lastName: v.optional(v.string()), | 27 | lastName: v.optional(v.string()), |
| 12 | avatarUrl: v.optional(v.string()), | 28 | avatarUrl: v.optional(v.string()), |
| 13 | status: v.string(), | 29 | status: accountStatus, |
| 14 | createdAt: v.number(), | 30 | createdAt: v.number(), |
| 15 | updatedAt: v.number(), | 31 | updatedAt: v.number(), |
| 16 | lastLoginAt: v.optional(v.number()), | 32 | lastLoginAt: v.optional(v.number()), |
| @@ -39,7 +55,7 @@ export default defineSchema({ · 22 unmodified lines | |||
| 39 | workosOrgId: v.optional(v.string()), | 55 | workosOrgId: v.optional(v.string()), |
| 40 | slug: v.string(), | 56 | slug: v.string(), |
| 41 | name: v.string(), | 57 | name: v.string(), |
| 42 | status: v.string(), | 58 | status: organizationStatus, |
| 43 | createdByAccountId: v.optional(v.id("accounts")), | 59 | createdByAccountId: v.optional(v.id("accounts")), |
| 44 | createdAt: v.number(), | 60 | createdAt: v.number(), |
| 45 | updatedAt: v.number(), | 61 | updatedAt: v.number(), |
| @@ -51,8 +67,8 @@ export default defineSchema({ · 5 unmodified lines | |||
| 51 | organizationMembers: defineTable({ | 67 | organizationMembers: defineTable({ |
| 52 | organizationId: v.id("organizations"), | 68 | organizationId: v.id("organizations"), |
| 53 | accountId: v.id("accounts"), | 69 | accountId: v.id("accounts"), |
| 54 | role: v.string(), | 70 | role: orgRole, |
| 55 | status: v.string(), | 71 | status: orgMemberStatus, |
| 56 | billable: v.boolean(), | 72 | billable: v.boolean(), |
| 57 | invitedByAccountId: v.optional(v.id("accounts")), | 73 | invitedByAccountId: v.optional(v.id("accounts")), |
| 58 | joinedAt: v.optional(v.number()), | 74 | joinedAt: v.optional(v.number()), |
| @@ -68,37 +84,26 @@ export default defineSchema({ · 9 unmodified lines | |||
| 68 | workspaceMembers: defineTable({ | 84 | workspaceMembers: defineTable({ |
| 69 | workspaceId: v.id("workspaces"), | 85 | workspaceId: v.id("workspaces"), |
| 70 | accountId: v.id("accounts"), | 86 | accountId: v.id("accounts"), |
| 71 | role: v.string(), | 87 | workosOrgMembershipId: v.optional(v.string()), |
| 72 | status: v.string(), | 88 | role: workspaceMemberRole, |
| 89 | status: workspaceMemberStatus, | ||
| 73 | createdAt: v.number(), | 90 | createdAt: v.number(), |
| 74 | updatedAt: v.number(), | 91 | updatedAt: v.number(), |
| 75 | }) | 92 | }) |
| 76 | .index("by_workspace", ["workspaceId"]) | 93 | .index("by_workspace", ["workspaceId"]) |
| 77 | .index("by_workspace_account", ["workspaceId", "accountId"]) | 94 | .index("by_workspace_account", ["workspaceId", "accountId"]) |
| 78 | .index("by_account", ["accountId"]) | 95 | .index("by_account", ["accountId"]) |
| 79 | .index("by_workspace_status", ["workspaceId", "status"]), | 96 | .index("by_workspace_status", ["workspaceId", "status"]) |
| 80 | 97 | .index("by_workos_membership_id", ["workosOrgMembershipId"]), | |
| 81 | accountIdentities: defineTable({ | ||
| 82 | accountId: v.id("accounts"), | ||
| 83 | provider: v.string(), | ||
| 84 | providerUserId: v.string(), | ||
| 85 | providerEmail: v.optional(v.string()), | ||
| 86 | rawClaims: v.optional(v.any()), | ||
| 87 | lastLoginAt: v.optional(v.number()), | ||
| 88 | createdAt: v.number(), | ||
| 89 | updatedAt: v.number(), | ||
| 90 | }) | ||
| 91 | .index("by_account", ["accountId"]) | ||
| 92 | .index("by_provider_user", ["provider", "providerUserId"]), | ||
| 93 | 98 | ||
| 94 | invites: defineTable({ | 99 | invites: defineTable({ |
| 95 | organizationId: v.id("organizations"), | 100 | organizationId: v.id("organizations"), |
| 96 | workspaceId: v.optional(v.id("workspaces")), | 101 | workspaceId: v.optional(v.id("workspaces")), |
| 97 | email: v.string(), | 102 | email: v.string(), |
| 98 | role: v.string(), | 103 | role: orgRole, |
| 99 | status: v.string(), | 104 | status: inviteStatus, |
| 100 | tokenHash: v.string(), | 105 | tokenHash: v.string(), |
| 101 | provider: v.string(), | 106 | provider: inviteProvider, |
| 102 | providerInviteId: v.optional(v.string()), | 107 | providerInviteId: v.optional(v.string()), |
| 103 | invitedByAccountId: v.id("accounts"), | 108 | invitedByAccountId: v.id("accounts"), |
| 104 | expiresAt: v.number(), | 109 | expiresAt: v.number(), |
| @@ -146,20 +151,6 @@ export default defineSchema({ · 41 unmodified lines | |||
| 146 | updatedAt: v.number(), | 151 | updatedAt: v.number(), |
| 147 | }).index("by_org", ["organizationId"]), | 152 | }).index("by_org", ["organizationId"]), |
| 148 | 153 | ||
| 149 | users: defineTable({ | ||
| 150 | workspaceId: v.id("workspaces"), | ||
| 151 | accountId: v.id("accounts"), | ||
| 152 | workosOrgMembershipId: v.optional(v.string()), | ||
| 153 | role: v.string(), | ||
| 154 | status: v.string(), | ||
| 155 | createdAt: v.number(), | ||
| 156 | updatedAt: v.number(), | ||
| 157 | }) | ||
| 158 | .index("by_workspace_account", ["workspaceId", "accountId"]) | ||
| 159 | .index("by_account", ["accountId"]) | ||
| 160 | .index("by_workspace", ["workspaceId"]) | ||
| 161 | .index("by_workos_membership_id", ["workosOrgMembershipId"]), | ||
| 162 | |||
| 163 | tasks: defineTable({ | 154 | tasks: defineTable({ |
| 164 | taskId: v.string(), | 155 | taskId: v.string(), |
| 165 | code: v.string(), | 156 | code: v.string(), |
| @@ -277,8 +268,7 @@ export default defineSchema({ · 111 unmodified lines | |||
| 277 | actorId: v.string(), | 268 | actorId: v.string(), |
| 278 | clientId: v.string(), | 269 | clientId: v.string(), |
| 279 | accountId: v.optional(v.id("accounts")), | 270 | accountId: v.optional(v.id("accounts")), |
| 280 | workspaceDocId: v.optional(v.id("workspaces")), | 271 | userId: v.optional(v.id("workspaceMembers")), |
| 281 | userId: v.optional(v.id("users")), | ||
| 282 | createdAt: v.number(), | 272 | createdAt: v.number(), |
| 283 | lastSeenAt: v.number(), | 273 | lastSeenAt: v.number(), |
| 284 | }) | 274 | }) |
| @@ -11,7 +11,6 @@ type WorkspaceResult = { · 10 unmodified lines | |||
| 11 | name: string; | 11 | name: string; |
| 12 | slug: string; | 12 | slug: string; |
| 13 | iconUrl: string | null; | 13 | iconUrl: string | null; |
| 14 | runtimeWorkspaceId: string; | ||
| 15 | createdAt: number; | 14 | createdAt: number; |
| 16 | }; | 15 | }; |
| 17 | 16 | ||
| @@ -52,7 +51,6 @@ async function toWorkspaceResult( · 34 unmodified lines | |||
| 52 | name: workspace.name, | 51 | name: workspace.name, |
| 53 | slug: workspace.slug, | 52 | slug: workspace.slug, |
| 54 | iconUrl, | 53 | iconUrl, |
| 55 | runtimeWorkspaceId: workspace._id, | ||
| 56 | createdAt: workspace.createdAt, | 54 | createdAt: workspace.createdAt, |
| 57 | }; | 55 | }; |
| 58 | } | 56 | } |
| @@ -118,7 +118,6 @@ export interface AnonymousContext { · 117 unmodified lines | |||
| 118 | actorId: string; | 118 | actorId: string; |
| 119 | clientId: string; | 119 | clientId: string; |
| 120 | accountId?: string; | 120 | accountId?: string; |
| 121 | workspaceDocId?: string; | ||
| 122 | userId?: string; | 121 | userId?: string; |
| 123 | createdAt: number; | 122 | createdAt: number; |
| 124 | lastSeenAt: number; | 123 | lastSeenAt: number; |