Add member identifiers table for ID documents (DL, passport, school ID)

member_identifier table with type, value, issuing authority, expiry,
front/back image storage (base64 in Postgres), primary flag. CRUD
endpoints under /members/:memberId/identifiers. Zod schemas with
constrained type enum.
This commit is contained in:
Ryan Moon
2026-03-28 09:38:01 -05:00
parent 727275af59
commit c7b460c0bf
7 changed files with 208 additions and 0 deletions

View File

@@ -3,6 +3,7 @@ import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'
import {
accounts,
members,
memberIdentifiers,
accountProcessorLinks,
accountPaymentMethods,
taxExemptions,
@@ -10,6 +11,8 @@ import {
import type {
AccountCreateInput,
AccountUpdateInput,
MemberIdentifierCreateInput,
MemberIdentifierUpdateInput,
ProcessorLinkCreateInput,
ProcessorLinkUpdateInput,
PaymentMethodCreateInput,
@@ -564,3 +567,92 @@ export const TaxExemptionService = {
return exemption ?? null
},
}
export const MemberIdentifierService = {
async create(db: PostgresJsDatabase, companyId: string, input: MemberIdentifierCreateInput) {
// If setting as primary, unset existing primary for this member
if (input.isPrimary) {
await db
.update(memberIdentifiers)
.set({ isPrimary: false })
.where(
and(
eq(memberIdentifiers.memberId, input.memberId),
eq(memberIdentifiers.isPrimary, true),
),
)
}
const [identifier] = await db
.insert(memberIdentifiers)
.values({
companyId,
memberId: input.memberId,
type: input.type,
label: input.label,
value: input.value,
issuingAuthority: input.issuingAuthority,
issuedDate: input.issuedDate,
expiresAt: input.expiresAt,
imageFrontUrl: input.imageFrontUrl,
imageBackUrl: input.imageBackUrl,
notes: input.notes,
isPrimary: input.isPrimary,
})
.returning()
return identifier
},
async listByMember(db: PostgresJsDatabase, companyId: string, memberId: string) {
return db
.select()
.from(memberIdentifiers)
.where(
and(
eq(memberIdentifiers.companyId, companyId),
eq(memberIdentifiers.memberId, memberId),
),
)
},
async getById(db: PostgresJsDatabase, companyId: string, id: string) {
const [identifier] = await db
.select()
.from(memberIdentifiers)
.where(and(eq(memberIdentifiers.id, id), eq(memberIdentifiers.companyId, companyId)))
.limit(1)
return identifier ?? null
},
async update(db: PostgresJsDatabase, companyId: string, id: string, input: MemberIdentifierUpdateInput) {
if (input.isPrimary) {
const existing = await this.getById(db, companyId, id)
if (existing) {
await db
.update(memberIdentifiers)
.set({ isPrimary: false })
.where(
and(
eq(memberIdentifiers.memberId, existing.memberId),
eq(memberIdentifiers.isPrimary, true),
),
)
}
}
const [identifier] = await db
.update(memberIdentifiers)
.set({ ...input, updatedAt: new Date() })
.where(and(eq(memberIdentifiers.id, id), eq(memberIdentifiers.companyId, companyId)))
.returning()
return identifier ?? null
},
async delete(db: PostgresJsDatabase, companyId: string, id: string) {
const [identifier] = await db
.delete(memberIdentifiers)
.where(and(eq(memberIdentifiers.id, id), eq(memberIdentifiers.companyId, companyId)))
.returning()
return identifier ?? null
},
}