1import { Inject, Injectable } from "@nestjs/common";
2import { PrismaService } from "../../common/prisma.service.js";
3import { AuditService } from "../../common/audit.service.js";
4import { ApiError } from "../../common/errors.js";
5import { loadEnv } from "../../config/env.js";
6import type { InternalAddMemberInput } from "./test-seam.dto.js";
7
8
31@Injectable()
32export class TestSeamService {
33
34
35 constructor(
36 @Inject(PrismaService) private readonly prisma: PrismaService,
37 @Inject(AuditService) private readonly audit: AuditService
38 ) {}
39
40
44 async addMember(workspaceId: string, input: InternalAddMemberInput) {
45 if (loadEnv().INTERNAL_TEST_SEAM !== "1") {
46
47
48 throw ApiError.unauthorized("Test seam is disabled.", [{ reason: "test_seam_disabled" }]);
49 }
50 const ws = await this.prisma.workspace.findUnique({
51 where: { id: workspaceId },
52 select: { id: true, deletedAt: true }
53 });
54 if (!ws || ws.deletedAt) throw ApiError.notFound("Workspace not found.");
55 const user = await this.prisma.user.findUnique({
56 where: { email: input.email.toLowerCase() },
57 select: { id: true, email: true }
58 });
59 if (!user) throw ApiError.notFound("User not found — register the user first.");
60 const member = await this.prisma.workspaceMember.upsert({
61 where: { workspaceId_userId: { workspaceId, userId: user.id } },
62 update: {},
63 create: { workspaceId, userId: user.id, role: input.role as never, joinedAt: new Date() }
64 });
65 await this.audit.write({
66 action: "workspace.test_member_added",
67 resourceType: "workspace_member",
68 resourceId: member.id,
69 workspaceId,
70 metadata: { email: user.email, role: input.role, seam: true }
71 });
72 return { id: member.id, workspaceId: member.workspaceId, userId: member.userId, role: member.role };
73 }
74}
75