52 lines
1.4 KiB
TypeScript
52 lines
1.4 KiB
TypeScript
import type { Kysely } from 'kysely'
|
|
import type { DB } from '../types/db.types.js'
|
|
|
|
/**
|
|
* Ensures a login-capable client user exists for a client org, idempotently.
|
|
*
|
|
* Finds or creates a `user` row (role `client`) keyed by lower-cased email,
|
|
* then ensures a `client_member` row links that user to the client as `organiser`.
|
|
*
|
|
* Reuse-by-email is intentional: one person who organises for several clients
|
|
* gets one login with multiple `client_member` rows.
|
|
* Returns the resolved `userId`.
|
|
*/
|
|
export async function provisionClientLogin(
|
|
kysely: Kysely<DB>,
|
|
{ clientId, email, name }: { clientId: string; email: string; name: string | null },
|
|
): Promise<string> {
|
|
const normalisedEmail = email.toLowerCase()
|
|
|
|
const existing = await kysely
|
|
.selectFrom('user')
|
|
.where('email', '=', normalisedEmail)
|
|
.select('id')
|
|
.executeTakeFirst()
|
|
|
|
const id = existing?.id ?? crypto.randomUUID()
|
|
if (!existing) {
|
|
const now = new Date().toISOString()
|
|
await kysely
|
|
.insertInto('user')
|
|
.values({
|
|
id,
|
|
email: normalisedEmail,
|
|
name: name ?? normalisedEmail,
|
|
emailVerified: 0,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
role: 'client',
|
|
locale: 'de',
|
|
} as any)
|
|
.execute()
|
|
}
|
|
|
|
await kysely
|
|
.insertInto('clientMember')
|
|
.values({ clientId, userId: id, role: 'organiser' })
|
|
.onConflict((oc) => oc.columns(['clientId', 'userId']).doNothing())
|
|
.execute()
|
|
|
|
return id
|
|
}
|