test: verify duplicate-safe Discord reconciliation
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import type { AdapterAccount } from "next-auth/adapters";
|
||||
|
||||
export type DiscordIdentity = {
|
||||
discordId: string;
|
||||
displayName: string;
|
||||
avatarUrl: string | null;
|
||||
email?: string | null;
|
||||
account?: Partial<AdapterAccount>;
|
||||
};
|
||||
|
||||
export function discordAccountUpdate(
|
||||
account: Partial<AdapterAccount> | undefined,
|
||||
) {
|
||||
if (!account) return {};
|
||||
return {
|
||||
...(account.type !== undefined ? { type: account.type } : {}),
|
||||
...(account.refresh_token !== undefined
|
||||
? { refresh_token: account.refresh_token }
|
||||
: {}),
|
||||
...(account.access_token !== undefined
|
||||
? { access_token: account.access_token }
|
||||
: {}),
|
||||
...(account.expires_at !== undefined
|
||||
? { expires_at: account.expires_at }
|
||||
: {}),
|
||||
...(account.token_type !== undefined
|
||||
? { token_type: account.token_type }
|
||||
: {}),
|
||||
...(account.scope !== undefined ? { scope: account.scope } : {}),
|
||||
...(account.id_token !== undefined ? { id_token: account.id_token } : {}),
|
||||
...(account.session_state !== undefined
|
||||
? { session_state: account.session_state as string | null }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
export type DiscordAccountUpdate = ReturnType<typeof discordAccountUpdate>;
|
||||
|
||||
export type DiscordUserOperations<TUser> = {
|
||||
findLinkedUserId(discordId: string): Promise<string | null>;
|
||||
upsertUser(identity: DiscordIdentity): Promise<string>;
|
||||
insertAccount(
|
||||
userId: string,
|
||||
discordId: string,
|
||||
account: Partial<AdapterAccount> | undefined,
|
||||
): Promise<void>;
|
||||
updateUser(userId: string, identity: DiscordIdentity): Promise<TUser | null>;
|
||||
updateAccount(
|
||||
discordId: string,
|
||||
update: DiscordAccountUpdate,
|
||||
): Promise<void>;
|
||||
};
|
||||
|
||||
export async function reconcileDiscordUser<TUser>(
|
||||
identity: DiscordIdentity,
|
||||
operations: DiscordUserOperations<TUser>,
|
||||
) {
|
||||
const { discordId, account } = identity;
|
||||
let userId = await operations.findLinkedUserId(discordId);
|
||||
|
||||
if (!userId) {
|
||||
userId = await operations.upsertUser(identity);
|
||||
await operations.insertAccount(userId, discordId, account);
|
||||
|
||||
const winner = await operations.findLinkedUserId(discordId);
|
||||
if (!winner) throw new Error("Failed to link Discord account");
|
||||
userId = winner;
|
||||
}
|
||||
|
||||
const user = await operations.updateUser(userId, identity);
|
||||
const nextAccount = discordAccountUpdate(account);
|
||||
if (Object.keys(nextAccount).length > 0) {
|
||||
await operations.updateAccount(discordId, nextAccount);
|
||||
}
|
||||
|
||||
if (!user) throw new Error("Failed to reconcile Discord user");
|
||||
return user;
|
||||
}
|
||||
Reference in New Issue
Block a user