TTeamDesk 学习手册

src/server/services/access.ts

此代码来自构建时的仓库快照。修改源码后重新构建即可更新。

ts
import { TRPCError } from '@trpc/server';
import type { Prisma, PrismaClient } from '@/generated/prisma/client';

type Db = PrismaClient | Prisma.TransactionClient;
export async function requireMembership(
  db: Db,
  userId: string,
  teamId: string,
) {
  // teamId 相当于客户端选择的“工作区”,不是权限证明;每次请求都查真实成员关系。
  const member = await db.membership.findUnique({
    where: { teamId_userId: { teamId, userId } },
  });
  if (!member)
    throw new TRPCError({
      code: 'FORBIDDEN',
      message: '你不是该团队成员,或成员资格已被移除',
    });
  return member;
}
export async function requireAdmin(db: Db, userId: string, teamId: string) {
  const member = await requireMembership(db, userId, teamId);
  if (member.role !== 'ADMIN')
    throw new TRPCError({
      code: 'FORBIDDEN',
      message: '仅管理员可以管理团队成员',
    });
  return member;
}

export async function requireTicketAccess(
  db: Db,
  userId: string,
  teamId: string,
  id: string,
) {
  const member = await requireMembership(db, userId, teamId);
  // 工单详情、回复与操作记录共用这一可见性边界,避免新增接口漏掉客户归属条件。
  const ticket = await db.ticket.findFirst({
    where: {
      id,
      teamId,
      ...(member.role === 'CUSTOMER' ? { creatorId: userId } : {}),
    },
  });
  if (!ticket)
    throw new TRPCError({ code: 'NOT_FOUND', message: '工单不存在或无权访问' });
  return { ticket, member };
}

搜索教程

输入关键词,搜索全部章节。

阅读设置

外观
正文字号

从一张工单出发,读懂数据如何流转。

设置与进度仅保存在当前浏览器。