feat(mentions): switch to Convex-backed search so @<ref> and text queries return visible tickets for the current user; keep permissions
This commit is contained in:
parent
e6c841383e
commit
66fe34868c
1 changed files with 62 additions and 67 deletions
|
|
@ -1,13 +1,14 @@
|
|||
import { NextResponse } from "next/server"
|
||||
|
||||
import { ConvexHttpClient } from "convex/browser"
|
||||
import { api } from "@/convex/_generated/api"
|
||||
import type { Id } from "@/convex/_generated/dataModel"
|
||||
import { env } from "@/lib/env"
|
||||
import { assertAuthenticatedSession } from "@/lib/auth-server"
|
||||
import { DEFAULT_TENANT_ID } from "@/lib/constants"
|
||||
import { prisma } from "@/lib/prisma"
|
||||
|
||||
export const runtime = "nodejs"
|
||||
|
||||
const MAX_RESULTS = 10
|
||||
const MAX_SCAN = 60
|
||||
|
||||
function normalizeRole(role?: string | null) {
|
||||
return (role ?? "").toLowerCase()
|
||||
|
|
@ -19,10 +20,14 @@ export async function GET(request: Request) {
|
|||
return NextResponse.json({ items: [] }, { status: 401 })
|
||||
}
|
||||
|
||||
const convexUrl = env.NEXT_PUBLIC_CONVEX_URL
|
||||
if (!convexUrl) {
|
||||
return NextResponse.json({ items: [] }, { status: 500 })
|
||||
}
|
||||
|
||||
const normalizedRole = normalizeRole(session.user.role)
|
||||
const isAgentOrAdmin = normalizedRole === "admin" || normalizedRole === "agent"
|
||||
const canLinkOwnTickets = normalizedRole === "collaborator"
|
||||
|
||||
if (!isAgentOrAdmin && !canLinkOwnTickets) {
|
||||
return NextResponse.json({ items: [] }, { status: 403 })
|
||||
}
|
||||
|
|
@ -32,75 +37,65 @@ export async function GET(request: Request) {
|
|||
const rawQuery = url.searchParams.get("q") ?? ""
|
||||
const query = rawQuery.trim()
|
||||
|
||||
const whereBase: {
|
||||
tenantId: string
|
||||
requesterId?: string
|
||||
} = { tenantId }
|
||||
const client = new ConvexHttpClient(convexUrl)
|
||||
|
||||
if (!isAgentOrAdmin) {
|
||||
whereBase.requesterId = session.user.id
|
||||
// Garantir que o usuário exista no Convex para obter viewerId
|
||||
let viewerId: string | null = null
|
||||
try {
|
||||
const ensured = await client.mutation(api.users.ensureUser, {
|
||||
tenantId,
|
||||
name: session.user.name ?? session.user.email,
|
||||
email: session.user.email,
|
||||
avatarUrl: session.user.avatarUrl ?? undefined,
|
||||
role: session.user.role.toUpperCase(),
|
||||
})
|
||||
viewerId = ensured?._id ?? null
|
||||
} catch (error) {
|
||||
console.error("[mentions] ensureUser failed", error)
|
||||
return NextResponse.json({ items: [] }, { status: 500 })
|
||||
}
|
||||
if (!viewerId) {
|
||||
return NextResponse.json({ items: [] }, { status: 403 })
|
||||
}
|
||||
|
||||
const numericQuery = /^\d+$/.test(query)
|
||||
|
||||
// Fast path for numeric query: exact reference match at DB level
|
||||
let filtered: any[] = []
|
||||
if (numericQuery) {
|
||||
const ref = Number(query)
|
||||
const exact = await prisma.ticket.findMany({
|
||||
where: { ...whereBase, reference: ref },
|
||||
include: {
|
||||
assignee: { select: { name: true } },
|
||||
requester: { select: { name: true } },
|
||||
company: { select: { name: true } },
|
||||
},
|
||||
take: MAX_RESULTS,
|
||||
})
|
||||
filtered = exact
|
||||
} else {
|
||||
const tickets = await prisma.ticket.findMany({
|
||||
where: whereBase,
|
||||
include: {
|
||||
assignee: { select: { name: true } },
|
||||
requester: { select: { name: true } },
|
||||
company: { select: { name: true } },
|
||||
},
|
||||
orderBy: { updatedAt: "desc" },
|
||||
take: MAX_SCAN,
|
||||
})
|
||||
|
||||
const lowered = query.toLowerCase()
|
||||
filtered = tickets
|
||||
.filter((ticket) => {
|
||||
if (!query) return true
|
||||
const subject = ticket.subject ?? ""
|
||||
if (subject.toLowerCase().includes(lowered)) return true
|
||||
const requesterName = ticket.requester?.name ?? ""
|
||||
if (requesterName.toLowerCase().includes(lowered)) return true
|
||||
const companyName = ticket.company?.name ?? ""
|
||||
if (companyName.toLowerCase().includes(lowered)) return true
|
||||
return false
|
||||
})
|
||||
.slice(0, MAX_RESULTS)
|
||||
// Pesquisar pelos tickets visíveis ao viewer (assunto, resumo ou #referência)
|
||||
let tickets: Array<{
|
||||
id: string
|
||||
reference: number
|
||||
subject: string
|
||||
status: string
|
||||
priority: string
|
||||
requester?: { name?: string | null } | null
|
||||
assignee?: { name?: string | null } | null
|
||||
company?: { name?: string | null } | null
|
||||
updatedAt?: number | null
|
||||
}> = []
|
||||
try {
|
||||
const res = (await client.query(api.tickets.list, {
|
||||
tenantId,
|
||||
viewerId: viewerId as unknown as Id<"users">,
|
||||
search: query,
|
||||
limit: 40,
|
||||
})) as any[]
|
||||
tickets = Array.isArray(res) ? res : []
|
||||
} catch (error) {
|
||||
console.error("[mentions] tickets.list failed", error)
|
||||
return NextResponse.json({ items: [] }, { status: 500 })
|
||||
}
|
||||
|
||||
const basePath = isAgentOrAdmin ? "/tickets" : "/portal/tickets"
|
||||
|
||||
const items = filtered.map((ticket) => {
|
||||
const subject = ticket.subject ?? ""
|
||||
return {
|
||||
id: ticket.id,
|
||||
reference: ticket.reference,
|
||||
subject,
|
||||
status: ticket.status,
|
||||
priority: ticket.priority,
|
||||
requesterName: ticket.requester?.name ?? null,
|
||||
assigneeName: ticket.assignee?.name ?? null,
|
||||
companyName: ticket.company?.name ?? null,
|
||||
url: `${basePath}/${ticket.id}`,
|
||||
updatedAt: ticket.updatedAt.toISOString(),
|
||||
}
|
||||
})
|
||||
const items = tickets.slice(0, MAX_RESULTS).map((t) => ({
|
||||
id: String(t.id),
|
||||
reference: Number(t.reference),
|
||||
subject: String(t.subject ?? ""),
|
||||
status: String(t.status ?? "PENDING"),
|
||||
priority: String(t.priority ?? "MEDIUM"),
|
||||
requesterName: t.requester?.name ?? null,
|
||||
assigneeName: t.assignee?.name ?? null,
|
||||
companyName: t.company?.name ?? null,
|
||||
url: `${basePath}/${String(t.id)}`,
|
||||
updatedAt: t.updatedAt ? new Date(t.updatedAt).toISOString() : new Date().toISOString(),
|
||||
}))
|
||||
|
||||
return NextResponse.json({ items })
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue