167 lines
4.7 KiB
TypeScript
167 lines
4.7 KiB
TypeScript
import { db } from "@/lib/db/client"
|
|
|
|
export type MemberMutationResult =
|
|
| { status: "updated"; memberId: string }
|
|
| { status: "deleted"; memberId: string }
|
|
| { status: "not_found" }
|
|
| { status: "last_member" }
|
|
|
|
export async function listInvitationMembers(invitationId: string) {
|
|
return db
|
|
.selectFrom("invitation_members")
|
|
.selectAll()
|
|
.where("invitation_id", "=", invitationId)
|
|
.orderBy("is_primary_contact", "desc")
|
|
.orderBy("created_at", "asc")
|
|
.execute()
|
|
}
|
|
|
|
export async function updateInvitationMemberName({
|
|
invitationId,
|
|
memberId,
|
|
name,
|
|
adminEmail,
|
|
}: {
|
|
invitationId: string
|
|
memberId: string
|
|
name: string
|
|
adminEmail: string
|
|
}): Promise<MemberMutationResult> {
|
|
return db.transaction().execute(async (trx) => {
|
|
const member = await trx
|
|
.selectFrom("invitation_members")
|
|
.innerJoin("invitations", "invitations.id", "invitation_members.invitation_id")
|
|
.select(["invitation_members.id", "invitation_members.name", "invitations.display_name"])
|
|
.where("invitation_members.id", "=", memberId)
|
|
.where("invitation_members.invitation_id", "=", invitationId)
|
|
.executeTakeFirst()
|
|
|
|
if (!member) return { status: "not_found" }
|
|
|
|
const updated = await trx
|
|
.updateTable("invitation_members")
|
|
.set({ name, updated_at: new Date() })
|
|
.where("id", "=", memberId)
|
|
.where("invitation_id", "=", invitationId)
|
|
.returning("id")
|
|
.executeTakeFirst()
|
|
|
|
if (!updated) return { status: "not_found" }
|
|
|
|
await trx
|
|
.updateTable("invitations")
|
|
.set({ updated_at: new Date() })
|
|
.where("id", "=", invitationId)
|
|
.execute()
|
|
|
|
await trx
|
|
.insertInto("admin_audit_events")
|
|
.values({
|
|
admin_email: adminEmail,
|
|
action: "invitation_member.updated",
|
|
target_type: "invitation_member",
|
|
target_id: memberId,
|
|
metadata: {
|
|
invitationId,
|
|
displayName: member.display_name,
|
|
previousName: member.name,
|
|
newName: name,
|
|
},
|
|
})
|
|
.execute()
|
|
|
|
return { status: "updated", memberId }
|
|
})
|
|
}
|
|
|
|
export async function deleteInvitationMember({
|
|
invitationId,
|
|
memberId,
|
|
adminEmail,
|
|
}: {
|
|
invitationId: string
|
|
memberId: string
|
|
adminEmail: string
|
|
}): Promise<MemberMutationResult> {
|
|
return db.transaction().execute(async (trx) => {
|
|
const member = await trx
|
|
.selectFrom("invitation_members")
|
|
.innerJoin("invitations", "invitations.id", "invitation_members.invitation_id")
|
|
.select([
|
|
"invitation_members.id",
|
|
"invitation_members.name",
|
|
"invitation_members.is_primary_contact",
|
|
"invitations.display_name",
|
|
])
|
|
.where("invitation_members.id", "=", memberId)
|
|
.where("invitation_members.invitation_id", "=", invitationId)
|
|
.executeTakeFirst()
|
|
|
|
if (!member) return { status: "not_found" }
|
|
|
|
const count = await trx
|
|
.selectFrom("invitation_members")
|
|
.select((eb) => eb.fn.countAll<string>().as("count"))
|
|
.where("invitation_id", "=", invitationId)
|
|
.executeTakeFirstOrThrow()
|
|
const memberCount = Number(count.count)
|
|
|
|
if (memberCount <= 1) return { status: "last_member" }
|
|
|
|
let promotedMemberId: string | null = null
|
|
if (member.is_primary_contact) {
|
|
const replacement = await trx
|
|
.selectFrom("invitation_members")
|
|
.select(["id"])
|
|
.where("invitation_id", "=", invitationId)
|
|
.where("id", "!=", memberId)
|
|
.orderBy("created_at", "asc")
|
|
.executeTakeFirst()
|
|
|
|
if (replacement) {
|
|
promotedMemberId = replacement.id
|
|
await trx
|
|
.updateTable("invitation_members")
|
|
.set({ is_primary_contact: true, updated_at: new Date() })
|
|
.where("id", "=", replacement.id)
|
|
.execute()
|
|
}
|
|
}
|
|
|
|
const deleted = await trx
|
|
.deleteFrom("invitation_members")
|
|
.where("id", "=", memberId)
|
|
.where("invitation_id", "=", invitationId)
|
|
.returning("id")
|
|
.executeTakeFirst()
|
|
|
|
if (!deleted) return { status: "not_found" }
|
|
|
|
await trx
|
|
.updateTable("invitations")
|
|
.set({ updated_at: new Date() })
|
|
.where("id", "=", invitationId)
|
|
.execute()
|
|
|
|
await trx
|
|
.insertInto("admin_audit_events")
|
|
.values({
|
|
admin_email: adminEmail,
|
|
action: "invitation_member.deleted",
|
|
target_type: "invitation_member",
|
|
target_id: memberId,
|
|
metadata: {
|
|
invitationId,
|
|
displayName: member.display_name,
|
|
name: member.name,
|
|
wasPrimary: member.is_primary_contact,
|
|
promotedMemberId,
|
|
remainingMembers: memberCount - 1,
|
|
},
|
|
})
|
|
.execute()
|
|
|
|
return { status: "deleted", memberId }
|
|
})
|
|
}
|