Phase 2: multi-user links for machines (Convex schema + mutations + admin API); UI to add/remove links; user editor lists machines via linkedUsers

This commit is contained in:
codex-bot 2025-10-21 11:06:21 -03:00
parent 6653ef250e
commit 22f0768492
5 changed files with 208 additions and 10 deletions

View file

@ -715,6 +715,7 @@ export const resolveToken = mutation({
assignedUserEmail: machine.assignedUserEmail ?? null,
assignedUserName: machine.assignedUserName ?? null,
assignedUserRole: machine.assignedUserRole ?? null,
linkedUserIds: machine.linkedUserIds ?? [],
status: machine.status,
lastHeartbeatAt: machine.lastHeartbeatAt,
metadata: machine.metadata,
@ -796,6 +797,16 @@ export const listByTenant = query({
}
}
// linked users summary
const linkedUserIds = machine.linkedUserIds ?? []
const linkedUsers = await Promise.all(
linkedUserIds.map(async (id) => {
const u = await ctx.db.get(id)
if (!u) return null
return { id: u._id, email: u.email, name: u.name }
})
).then((arr) => arr.filter(Boolean) as Array<{ id: string; email: string; name: string }>)
return {
id: machine._id,
tenantId: machine.tenantId,
@ -814,6 +825,7 @@ export const listByTenant = query({
assignedUserEmail: machine.assignedUserEmail ?? null,
assignedUserName: machine.assignedUserName ?? null,
assignedUserRole: machine.assignedUserRole ?? null,
linkedUsers,
status: derivedStatus,
isActive: machine.isActive ?? true,
lastHeartbeatAt: machine.lastHeartbeatAt ?? null,
@ -989,6 +1001,15 @@ export const getContext = query({
throw new ConvexError("Máquina não encontrada")
}
const linkedUserIds = machine.linkedUserIds ?? []
const linkedUsers = await Promise.all(
linkedUserIds.map(async (id) => {
const u = await ctx.db.get(id)
if (!u) return null
return { id: u._id, email: u.email, name: u.name }
})
).then((arr) => arr.filter(Boolean) as Array<{ id: string; email: string; name: string }>)
return {
id: machine._id,
tenantId: machine.tenantId,
@ -1002,6 +1023,7 @@ export const getContext = query({
metadata: machine.metadata ?? null,
authEmail: machine.authEmail ?? null,
isActive: machine.isActive ?? true,
linkedUsers,
}
},
})
@ -1050,6 +1072,44 @@ export const linkAuthAccount = mutation({
},
})
export const linkUser = mutation({
args: {
machineId: v.id("machines"),
email: v.string(),
},
handler: async (ctx, { machineId, email }) => {
const machine = await ctx.db.get(machineId)
if (!machine) throw new ConvexError("Máquina não encontrada")
const tenantId = machine.tenantId
const normalized = email.trim().toLowerCase()
const user = await ctx.db
.query("users")
.withIndex("by_tenant_email", (q) => q.eq("tenantId", tenantId).eq("email", normalized))
.first()
if (!user) throw new ConvexError("Usuário não encontrado")
const current = new Set((machine.linkedUserIds ?? []).map((id) => id.id ?? id))
current.add(user._id)
await ctx.db.patch(machine._id, { linkedUserIds: Array.from(current) as any, updatedAt: Date.now() })
return { ok: true }
},
})
export const unlinkUser = mutation({
args: {
machineId: v.id("machines"),
userId: v.id("users"),
},
handler: async (ctx, { machineId, userId }) => {
const machine = await ctx.db.get(machineId)
if (!machine) throw new ConvexError("Máquina não encontrada")
const next = (machine.linkedUserIds ?? []).filter((id) => id !== userId)
await ctx.db.patch(machine._id, { linkedUserIds: next, updatedAt: Date.now() })
return { ok: true }
},
})
export const rename = mutation({
args: {
machineId: v.id("machines"),

View file

@ -288,6 +288,7 @@ export default defineSchema({
assignedUserEmail: v.optional(v.string()),
assignedUserName: v.optional(v.string()),
assignedUserRole: v.optional(v.string()),
linkedUserIds: v.optional(v.array(v.id("users"))),
hostname: v.string(),
osName: v.string(),
osVersion: v.optional(v.string()),