Fix form template labels and guard admin auth tables
This commit is contained in:
parent
003d068c56
commit
7fb6c65d9a
2 changed files with 113 additions and 65 deletions
|
|
@ -80,6 +80,21 @@ function plainTextLength(html: string): number {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveFormTemplateLabel(
|
||||||
|
templateKey: string | null | undefined,
|
||||||
|
storedLabel: string | null | undefined
|
||||||
|
): string | null {
|
||||||
|
if (storedLabel && storedLabel.trim().length > 0) {
|
||||||
|
return storedLabel.trim();
|
||||||
|
}
|
||||||
|
const normalizedKey = templateKey?.trim();
|
||||||
|
if (!normalizedKey) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const fallback = TICKET_FORM_CONFIG.find((entry) => entry.key === normalizedKey);
|
||||||
|
return fallback ? fallback.label : null;
|
||||||
|
}
|
||||||
|
|
||||||
function escapeHtml(input: string): string {
|
function escapeHtml(input: string): string {
|
||||||
return input
|
return input
|
||||||
.replace(/&/g, "&")
|
.replace(/&/g, "&")
|
||||||
|
|
@ -1281,8 +1296,8 @@ export const list = query({
|
||||||
csatComment: typeof t.csatComment === "string" && t.csatComment.trim().length > 0 ? t.csatComment.trim() : null,
|
csatComment: typeof t.csatComment === "string" && t.csatComment.trim().length > 0 ? t.csatComment.trim() : null,
|
||||||
csatRatedAt: t.csatRatedAt ?? null,
|
csatRatedAt: t.csatRatedAt ?? null,
|
||||||
csatRatedBy: t.csatRatedBy ? String(t.csatRatedBy) : null,
|
csatRatedBy: t.csatRatedBy ? String(t.csatRatedBy) : null,
|
||||||
formTemplate: t.formTemplate ?? null,
|
formTemplate: t.formTemplate ?? null,
|
||||||
formTemplateLabel: t.formTemplateLabel ?? null,
|
formTemplateLabel: resolveFormTemplateLabel(t.formTemplate ?? null, t.formTemplateLabel ?? null),
|
||||||
company: company
|
company: company
|
||||||
? { id: company._id, name: company.name, isAvulso: company.isAvulso ?? false }
|
? { id: company._id, name: company.name, isAvulso: company.isAvulso ?? false }
|
||||||
: t.companyId || t.companySnapshot
|
: t.companyId || t.companySnapshot
|
||||||
|
|
@ -1592,7 +1607,7 @@ export const getById = query({
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
formTemplate: t.formTemplate ?? null,
|
formTemplate: t.formTemplate ?? null,
|
||||||
formTemplateLabel: t.formTemplateLabel ?? null,
|
formTemplateLabel: resolveFormTemplateLabel(t.formTemplate ?? null, t.formTemplateLabel ?? null),
|
||||||
chatEnabled: Boolean(t.chatEnabled),
|
chatEnabled: Boolean(t.chatEnabled),
|
||||||
relatedTicketIds: Array.isArray(t.relatedTicketIds) ? t.relatedTicketIds.map((id) => String(id)) : [],
|
relatedTicketIds: Array.isArray(t.relatedTicketIds) ? t.relatedTicketIds.map((id) => String(id)) : [],
|
||||||
resolvedWithTicketId: t.resolvedWithTicketId ? String(t.resolvedWithTicketId) : null,
|
resolvedWithTicketId: t.resolvedWithTicketId ? String(t.resolvedWithTicketId) : null,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { Prisma } from "@prisma/client"
|
||||||
|
|
||||||
import { AdminUsersManager } from "@/components/admin/admin-users-manager"
|
import { AdminUsersManager } from "@/components/admin/admin-users-manager"
|
||||||
import { AppShell } from "@/components/app-shell"
|
import { AppShell } from "@/components/app-shell"
|
||||||
import { SiteHeader } from "@/components/site-header"
|
import { SiteHeader } from "@/components/site-header"
|
||||||
|
|
@ -10,80 +12,111 @@ import { getServerSession } from "@/lib/auth-server"
|
||||||
export const runtime = "nodejs"
|
export const runtime = "nodejs"
|
||||||
export const dynamic = "force-dynamic"
|
export const dynamic = "force-dynamic"
|
||||||
|
|
||||||
async function loadUsers() {
|
function isMissingAuthTableError(error: unknown, table: string): boolean {
|
||||||
const users = await prisma.authUser.findMany({
|
if (!(error instanceof Prisma.PrismaClientKnownRequestError)) {
|
||||||
orderBy: { createdAt: "desc" },
|
return false
|
||||||
select: {
|
}
|
||||||
id: true,
|
if (error.code !== "P2021" && error.code !== "P2023") {
|
||||||
email: true,
|
return false
|
||||||
name: true,
|
}
|
||||||
role: true,
|
const target = typeof error.meta?.table === "string" ? error.meta.table.toLowerCase() : ""
|
||||||
tenantId: true,
|
return target.includes(table.toLowerCase())
|
||||||
machinePersona: true,
|
}
|
||||||
createdAt: true,
|
|
||||||
updatedAt: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const domainUsers = await prisma.user.findMany({
|
async function loadUsers() {
|
||||||
select: {
|
try {
|
||||||
email: true,
|
const users = await prisma.authUser.findMany({
|
||||||
companyId: true,
|
orderBy: { createdAt: "desc" },
|
||||||
company: {
|
select: {
|
||||||
select: {
|
id: true,
|
||||||
id: true,
|
email: true,
|
||||||
name: true,
|
name: true,
|
||||||
|
role: true,
|
||||||
|
tenantId: true,
|
||||||
|
machinePersona: true,
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const domainUsers = await prisma.user.findMany({
|
||||||
|
select: {
|
||||||
|
email: true,
|
||||||
|
companyId: true,
|
||||||
|
company: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
})
|
|
||||||
|
|
||||||
const domainByEmail = new Map<string, (typeof domainUsers)[number]>(
|
const domainByEmail = new Map<string, (typeof domainUsers)[number]>(
|
||||||
domainUsers.map(
|
domainUsers.map(
|
||||||
(user: (typeof domainUsers)[number]): [string, (typeof domainUsers)[number]] => [
|
(user: (typeof domainUsers)[number]): [string, (typeof domainUsers)[number]] => [
|
||||||
user.email.toLowerCase(),
|
user.email.toLowerCase(),
|
||||||
user,
|
user,
|
||||||
]
|
]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
return users.map((user: (typeof users)[number]) => {
|
return users.map((user: (typeof users)[number]) => {
|
||||||
const domain = domainByEmail.get(user.email.toLowerCase())
|
const domain = domainByEmail.get(user.email.toLowerCase())
|
||||||
const normalizedRole = (normalizeRole(user.role) ?? "agent") as RoleOption
|
const normalizedRole = (normalizeRole(user.role) ?? "agent") as RoleOption
|
||||||
return {
|
return {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
name: user.name ?? "",
|
name: user.name ?? "",
|
||||||
role: normalizedRole,
|
role: normalizedRole,
|
||||||
tenantId: user.tenantId ?? DEFAULT_TENANT_ID,
|
tenantId: user.tenantId ?? DEFAULT_TENANT_ID,
|
||||||
createdAt: user.createdAt.toISOString(),
|
createdAt: user.createdAt.toISOString(),
|
||||||
updatedAt: user.updatedAt?.toISOString() ?? null,
|
updatedAt: user.updatedAt?.toISOString() ?? null,
|
||||||
companyId: domain?.companyId ?? null,
|
companyId: domain?.companyId ?? null,
|
||||||
companyName: domain?.company?.name ?? null,
|
companyName: domain?.company?.name ?? null,
|
||||||
machinePersona: user.machinePersona ?? null,
|
machinePersona: user.machinePersona ?? null,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
if (isMissingAuthTableError(error, "AuthUser")) {
|
||||||
|
console.warn("[admin] auth tables missing; returning empty user list")
|
||||||
|
return []
|
||||||
}
|
}
|
||||||
})
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadInvites(): Promise<NormalizedInvite[]> {
|
async function loadInvites(): Promise<NormalizedInvite[]> {
|
||||||
const invites = await prisma.authInvite.findMany({
|
try {
|
||||||
orderBy: { createdAt: "desc" },
|
const invites = await prisma.authInvite.findMany({
|
||||||
include: {
|
orderBy: { createdAt: "desc" },
|
||||||
events: {
|
include: {
|
||||||
orderBy: { createdAt: "asc" },
|
events: {
|
||||||
|
orderBy: { createdAt: "asc" },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const now = new Date()
|
|
||||||
const cutoff = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
|
|
||||||
return invites
|
|
||||||
.map((invite: (typeof invites)[number]) => normalizeInvite(invite, now))
|
|
||||||
.filter((invite: NormalizedInvite) => {
|
|
||||||
if (invite.status !== "revoked") return true
|
|
||||||
if (!invite.revokedAt) return true
|
|
||||||
return new Date(invite.revokedAt) > cutoff
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const now = new Date()
|
||||||
|
const cutoff = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
|
||||||
|
return invites
|
||||||
|
.map((invite) => normalizeInvite(invite, now))
|
||||||
|
.filter((invite: NormalizedInvite) => {
|
||||||
|
if (invite.status !== "revoked") return true
|
||||||
|
if (!invite.revokedAt) return true
|
||||||
|
return new Date(invite.revokedAt) > cutoff
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
if (isMissingAuthTableError(error, "AuthInvite")) {
|
||||||
|
console.warn("[admin] auth invite tables missing; returning empty invite list")
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function AdminPage() {
|
export default async function AdminPage() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue